container_images/registry-image-forked/commands/in.go (190 lines of code) (raw):

package commands import ( "encoding/json" "fmt" "io" "io/ioutil" "os" "path/filepath" resource "github.com/GoogleCloudPlatform/guest-test-infra/container_images/registry-image-forked" "github.com/fatih/color" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" "github.com/google/go-containerregistry/pkg/v1/tarball" "github.com/sirupsen/logrus" ) // ImageMetadata is type ImageMetadata struct { Env []string `json:"env"` User string `json:"user"` } // In is type In struct { stdin io.Reader stderr io.Writer stdout io.Writer args []string } // NewIn is func NewIn( stdin io.Reader, stderr io.Writer, stdout io.Writer, args []string, ) *In { return &In{ stdin: stdin, stderr: stderr, stdout: stdout, args: args, } } // Execute is func (i *In) Execute() error { setupLogging(i.stderr) var req resource.InRequest decoder := json.NewDecoder(i.stdin) decoder.DisallowUnknownFields() err := decoder.Decode(&req) if err != nil { return fmt.Errorf("invalid payload: %s", err) } if req.Source.Debug { logrus.SetLevel(logrus.DebugLevel) } if len(i.args) < 2 { return fmt.Errorf("destination path not specified") } dest := i.args[1] if req.Source.AwsAccessKeyID != "" && req.Source.AwsSecretAccessKey != "" && req.Source.AwsRegion != "" { if !req.Source.AuthenticateToECR() { return fmt.Errorf("cannot authenticate with ECR") } } repo, err := req.Source.NewRepository() if err != nil { return fmt.Errorf("failed to resolve repository: %w", err) } tag := repo.Tag(req.Version.Tag) if !req.Params.SkipDownload { mirrorSource, hasMirror, err := req.Source.Mirror() if err != nil { return fmt.Errorf("failed to resolve mirror: %w", err) } usedMirror := false if hasMirror { err := downloadWithRetry(tag, mirrorSource, req.Params, req.Version, dest, i.stderr) if err != nil { logrus.Warnf("download from mirror %s failed: %s", mirrorSource.Repository, err) } else { usedMirror = true } } if !usedMirror { err := downloadWithRetry(tag, req.Source, req.Params, req.Version, dest, i.stderr) if err != nil { return fmt.Errorf("download failed: %w", err) } } } err = saveVersionInfo(dest, req.Version, req.Source.Repository) if err != nil { return fmt.Errorf("saving version info failed: %w", err) } err = json.NewEncoder(os.Stdout).Encode(resource.InResponse{ Version: req.Version, Metadata: append(req.Source.Metadata(), resource.MetadataField{ Name: "tag", Value: req.Version.Tag, }), }) if err != nil { return fmt.Errorf("could not marshal JSON: %s", err) } return nil } func downloadWithRetry(tag name.Tag, source resource.Source, params resource.GetParams, version resource.Version, dest string, stderr io.Writer) error { fmt.Fprintf(os.Stderr, "fetching %s@%s\n", color.GreenString(source.Repository), color.YellowString(version.Digest)) repo, err := source.NewRepository() if err != nil { return fmt.Errorf("resolve repository name: %w", err) } return resource.RetryOnRateLimit(func() error { opts, err := source.AuthOptions(repo, []string{transport.PullScope}) if err != nil { return err } image, err := remote.Image(repo.Digest(version.Digest), opts...) if err != nil { return fmt.Errorf("get image: %w", err) } err = saveImage(dest, tag, image, params.Format(), source.Debug, stderr) if err != nil { return fmt.Errorf("save image: %w", err) } return nil }) } func saveImage(dest string, tag name.Tag, image v1.Image, format string, debug bool, stderr io.Writer) error { switch format { case "oci": err := ociFormat(dest, tag, image) if err != nil { return fmt.Errorf("write oci image: %w", err) } case "rootfs": err := rootfsFormat(dest, image, debug, stderr) if err != nil { return fmt.Errorf("write rootfs: %w", err) } } return nil } func saveVersionInfo(dest string, version resource.Version, repo string) error { err := ioutil.WriteFile(filepath.Join(dest, "tag"), []byte(version.Tag), 0644) if err != nil { return fmt.Errorf("write image tag: %w", err) } err = ioutil.WriteFile(filepath.Join(dest, "digest"), []byte(version.Digest), 0644) if err != nil { return fmt.Errorf("write image digest: %w", err) } err = ioutil.WriteFile(filepath.Join(dest, "repository"), []byte(repo), 0644) if err != nil { return fmt.Errorf("write image repository: %w", err) } return nil } func ociFormat(dest string, tag name.Tag, image v1.Image) error { err := tarball.WriteToFile(filepath.Join(dest, "image.tar"), tag, image) if err != nil { return fmt.Errorf("write OCI image: %s", err) } return nil } func rootfsFormat(dest string, image v1.Image, debug bool, stderr io.Writer) error { err := unpackImage(filepath.Join(dest, "rootfs"), image, debug, stderr) if err != nil { return fmt.Errorf("extract image: %w", err) } cfg, err := image.ConfigFile() if err != nil { return fmt.Errorf("inspect image config: %w", err) } meta, err := os.Create(filepath.Join(dest, "metadata.json")) if err != nil { return fmt.Errorf("create image metadata: %w", err) } env := cfg.Config.Env user := cfg.Config.User err = json.NewEncoder(meta).Encode(ImageMetadata{ Env: env, User: user, }) if err != nil { return fmt.Errorf("write image metadata: %w", err) } err = meta.Close() if err != nil { return fmt.Errorf("close image metadata file: %w", err) } return nil }