2021-07-30 13:19:40 -04:00
// Copyright 2021 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package commands
import (
"archive/tar"
2021-11-05 13:26:09 -04:00
"errors"
2021-07-30 13:19:40 -04:00
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/spf13/cobra"
)
// addDeps augments our CLI surface with deps.
func addDeps ( topLevel * cobra . Command ) {
deps := & cobra . Command {
Use : "deps IMAGE" ,
Short : "Print Go module dependency information about the ko-built binary in the image" ,
Long : ` This sub - command finds and extracts the executable binary in the image , assuming it was built by ko , and prints information about the Go module dependencies of that executable , as reported by "go version -m" .
If the image was not built using ko , or if it was built without embedding dependency information , this command will fail . ` ,
Example : `
# Fetch and extract Go dependency information from an image :
ko deps docker . io / my - user / my - image : v3 ` ,
Args : cobra . ExactArgs ( 1 ) ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
2021-11-02 17:15:25 -04:00
ctx := cmd . Context ( )
2021-07-30 13:19:40 -04:00
ref , err := name . ParseReference ( args [ 0 ] )
if err != nil {
return err
}
img , err := remote . Image ( ref ,
remote . WithContext ( ctx ) ,
remote . WithAuthFromKeychain ( authn . DefaultKeychain ) ,
remote . WithUserAgent ( ua ( ) ) )
if err != nil {
return err
}
cfg , err := img . ConfigFile ( )
if err != nil {
return err
}
ep := cfg . Config . Entrypoint
if len ( ep ) != 1 {
return fmt . Errorf ( "unexpected entrypoint: %s" , ep )
}
bin := ep [ 0 ]
rc := mutate . Extract ( img )
defer rc . Close ( )
tr := tar . NewReader ( rc )
for {
// Stop reading if the context is cancelled.
select {
case <- ctx . Done ( ) :
return ctx . Err ( )
default :
// keep reading.
}
h , err := tr . Next ( )
2021-11-05 13:26:09 -04:00
if errors . Is ( err , io . EOF ) {
2021-07-30 13:19:40 -04:00
return fmt . Errorf ( "no ko-built executable named %q found" , bin )
}
if err != nil {
return err
}
if h . Typeflag != tar . TypeReg {
continue
}
if h . Name != bin {
continue
}
2021-10-01 14:20:21 -04:00
tmp , err := ioutil . TempFile ( "" , filepath . Base ( filepath . Clean ( h . Name ) ) )
2021-07-30 13:19:40 -04:00
if err != nil {
return err
}
n := tmp . Name ( )
defer os . RemoveAll ( n ) // best effort: remove tmp file afterwards.
defer tmp . Close ( ) // close it first.
// io.LimitReader to appease gosec...
if _ , err := io . Copy ( tmp , io . LimitReader ( tr , h . Size ) ) ; err != nil {
return err
}
if err := os . Chmod ( n , os . FileMode ( h . Mode ) ) ; err != nil {
return err
}
cmd := exec . CommandContext ( ctx , "go" , "version" , "-m" , n )
cmd . Stdout = os . Stdout
cmd . Stderr = os . Stderr
return cmd . Run ( )
}
// unreachable
} ,
}
topLevel . AddCommand ( deps )
}