package main import ( "bytes" "context" "encoding/json" "errors" "fmt" "os" "strings" "github.com/containers/buildah/pkg/cli" "github.com/containers/buildah/pkg/parse" "github.com/containers/buildah/util" "github.com/containers/common/libimage" "github.com/containers/common/libimage/manifests" "github.com/containers/common/pkg/auth" cp "github.com/containers/image/v5/copy" "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/pkg/compression" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/hashicorp/go-multierror" digest "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) type manifestCreateOpts = struct { os, arch string all, tlsVerify, insecure, amend bool } type manifestAddOpts = struct { authfile, certDir, creds, os, arch, variant, osVersion string features, osFeatures, annotations []string tlsVerify, insecure, all bool } type manifestRemoveOpts = struct{} type manifestAnnotateOpts = struct { os, arch, variant, osVersion string features, osFeatures, annotations []string } type manifestInspectOpts = struct { authfile string tlsVerify bool } func init() { var ( manifestDescription = "\n Creates, modifies, and pushes manifest lists and image indexes." manifestCreateDescription = "\n Creates manifest lists and image indexes." manifestAddDescription = "\n Adds an image to a manifest list or image index." manifestRemoveDescription = "\n Removes an image from a manifest list or image index." manifestAnnotateDescription = "\n Adds or updates information about an entry in a manifest list or image index." manifestInspectDescription = "\n Display the contents of a manifest list or image index." manifestPushDescription = "\n Pushes manifest lists and image indexes to registries." manifestRmDescription = "\n Remove one or more manifest lists from local storage." manifestExistsDescription = "\n Check if a manifest list exists in local storage." manifestCreateOpts manifestCreateOpts manifestAddOpts manifestAddOpts manifestRemoveOpts manifestRemoveOpts manifestAnnotateOpts manifestAnnotateOpts manifestInspectOpts manifestInspectOpts manifestPushOpts pushOptions ) manifestCommand := &cobra.Command{ Use: "manifest", Short: "Manipulate manifest lists and image indexes", Long: manifestDescription, Example: `buildah manifest create localhost/list buildah manifest add localhost/list localhost/image buildah manifest annotate --annotation A=B localhost/list localhost/image buildah manifest annotate --annotation A=B localhost/list sha256:entryManifestDigest buildah manifest inspect localhost/list buildah manifest push localhost/list transport:destination buildah manifest remove localhost/list sha256:entryManifestDigest buildah manifest rm localhost/list`, } manifestCommand.SetUsageTemplate(UsageTemplate()) rootCmd.AddCommand(manifestCommand) manifestCreateCommand := &cobra.Command{ Use: "create", Short: "Create manifest list or image index", Long: manifestCreateDescription, RunE: func(cmd *cobra.Command, args []string) error { return manifestCreateCmd(cmd, args, manifestCreateOpts) }, Example: `buildah manifest create mylist:v1.11 buildah manifest create mylist:v1.11 arch-specific-image-to-add buildah manifest create --all mylist:v1.11 transport:tagged-image-to-add`, Args: cobra.MinimumNArgs(1), } manifestCreateCommand.SetUsageTemplate(UsageTemplate()) flags := manifestCreateCommand.Flags() flags.BoolVar(&manifestCreateOpts.all, "all", false, "add all of the lists' images if the images to add are lists") flags.BoolVar(&manifestCreateOpts.amend, "amend", false, "modify an existing list if one with the desired name already exists") flags.StringVar(&manifestCreateOpts.os, "os", "", "if any of the specified images is a list, choose the one for `os`") if err := flags.MarkHidden("os"); err != nil { panic(fmt.Sprintf("error marking --os as hidden: %v", err)) } flags.StringVar(&manifestCreateOpts.arch, "arch", "", "if any of the specified images is a list, choose the one for `arch`") if err := flags.MarkHidden("arch"); err != nil { panic(fmt.Sprintf("error marking --arch as hidden: %v", err)) } flags.BoolVar(&manifestCreateOpts.insecure, "insecure", false, "neither require HTTPS nor verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.") if err := flags.MarkHidden("insecure"); err != nil { panic(fmt.Sprintf("error marking insecure as hidden: %v", err)) } flags.BoolVar(&manifestCreateOpts.tlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.") flags.SetNormalizeFunc(cli.AliasFlags) manifestCommand.AddCommand(manifestCreateCommand) manifestAddCommand := &cobra.Command{ Use: "add", Short: "Add images to a manifest list or image index", Long: manifestAddDescription, RunE: func(cmd *cobra.Command, args []string) error { return manifestAddCmd(cmd, args, manifestAddOpts) }, Example: `buildah manifest add mylist:v1.11 image:v1.11-amd64 buildah manifest add mylist:v1.11 transport:imageName`, Args: cobra.MinimumNArgs(2), } manifestAddCommand.SetUsageTemplate(UsageTemplate()) flags = manifestAddCommand.Flags() flags.StringVar(&manifestAddOpts.authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&manifestAddOpts.certDir, "cert-dir", "", "use certificates at the specified path to access the registry") flags.StringVar(&manifestAddOpts.creds, "creds", "", "use `[username[:password]]` for accessing the registry") flags.StringVar(&manifestAddOpts.os, "os", "", "override the `OS` of the specified image") flags.StringVar(&manifestAddOpts.arch, "arch", "", "override the `architecture` of the specified image") flags.StringVar(&manifestAddOpts.variant, "variant", "", "override the `variant` of the specified image") flags.StringVar(&manifestAddOpts.osVersion, "os-version", "", "override the OS `version` of the specified image") flags.StringSliceVar(&manifestAddOpts.features, "features", nil, "override the `features` of the specified image") flags.StringSliceVar(&manifestAddOpts.osFeatures, "os-features", nil, "override the OS `features` of the specified image") flags.StringSliceVar(&manifestAddOpts.annotations, "annotation", nil, "set an `annotation` for the specified image") flags.BoolVar(&manifestAddOpts.insecure, "insecure", false, "neither require HTTPS nor verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.") if err := flags.MarkHidden("insecure"); err != nil { panic(fmt.Sprintf("error marking insecure as hidden: %v", err)) } flags.BoolVar(&manifestAddOpts.tlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.") flags.BoolVar(&manifestAddOpts.all, "all", false, "add all of the list's images if the image is a list") flags.SetNormalizeFunc(cli.AliasFlags) manifestCommand.AddCommand(manifestAddCommand) manifestRemoveCommand := &cobra.Command{ Use: "remove", Short: "Remove an entry from a manifest list or image index", Long: manifestRemoveDescription, RunE: func(cmd *cobra.Command, args []string) error { return manifestRemoveCmd(cmd, args, manifestRemoveOpts) }, Example: `buildah manifest remove mylist:v1.11 sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736`, Args: cobra.MinimumNArgs(2), } manifestRemoveCommand.SetUsageTemplate(UsageTemplate()) manifestCommand.AddCommand(manifestRemoveCommand) manifestExistsCommand := &cobra.Command{ Use: "exists", Short: "Check if a manifest list exists in local storage", Long: manifestExistsDescription, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { return manifestExistsCmd(cmd, args) }, Example: "buildah manifest exists mylist", } manifestExistsCommand.SetUsageTemplate(UsageTemplate()) manifestCommand.AddCommand(manifestExistsCommand) manifestAnnotateCommand := &cobra.Command{ Use: "annotate", Short: "Add or update information about an entry in a manifest list or image index", Long: manifestAnnotateDescription, RunE: func(cmd *cobra.Command, args []string) error { return manifestAnnotateCmd(cmd, args, manifestAnnotateOpts) }, Example: `buildah manifest annotate --annotation left=right mylist:v1.11 image:v1.11-amd64`, Args: cobra.MinimumNArgs(2), } flags = manifestAnnotateCommand.Flags() flags.StringVar(&manifestAnnotateOpts.os, "os", "", "override the `OS` of the specified image") flags.StringVar(&manifestAnnotateOpts.arch, "arch", "", "override the `Architecture` of the specified image") flags.StringVar(&manifestAnnotateOpts.variant, "variant", "", "override the `Variant` of the specified image") flags.StringVar(&manifestAnnotateOpts.osVersion, "os-version", "", "override the os `version` of the specified image") flags.StringSliceVar(&manifestAnnotateOpts.features, "features", nil, "override the `features` of the specified image") flags.StringSliceVar(&manifestAnnotateOpts.osFeatures, "os-features", nil, "override the os `features` of the specified image") flags.StringSliceVar(&manifestAnnotateOpts.annotations, "annotation", nil, "set an `annotation` for the specified image") manifestAnnotateCommand.SetUsageTemplate(UsageTemplate()) manifestCommand.AddCommand(manifestAnnotateCommand) manifestInspectCommand := &cobra.Command{ Use: "inspect", Short: "Display the contents of a manifest list or image index", Long: manifestInspectDescription, RunE: func(cmd *cobra.Command, args []string) error { return manifestInspectCmd(cmd, args, manifestInspectOpts) }, Example: `buildah manifest inspect mylist:v1.11`, Args: cobra.MinimumNArgs(1), } flags = manifestInspectCommand.Flags() flags.StringVar(&manifestInspectOpts.authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.BoolVar(&manifestInspectOpts.tlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.") manifestInspectCommand.SetUsageTemplate(UsageTemplate()) manifestCommand.AddCommand(manifestInspectCommand) manifestPushCommand := &cobra.Command{ Use: "push", Short: "Push a manifest list or image index to a registry", Long: manifestPushDescription, RunE: func(cmd *cobra.Command, args []string) error { return manifestPushCmd(cmd, args, manifestPushOpts) }, Example: `buildah manifest push mylist:v1.11 transport:imageName`, Args: cobra.MinimumNArgs(1), } manifestPushCommand.SetUsageTemplate(UsageTemplate()) flags = manifestPushCommand.Flags() flags.BoolVar(&manifestPushOpts.rm, "rm", false, "remove the manifest list if push succeeds") flags.BoolVar(&manifestPushOpts.all, "all", false, "also push the images in the list") flags.StringVar(&manifestPushOpts.authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&manifestPushOpts.certDir, "cert-dir", "", "use certificates at the specified path to access the registry") flags.StringVar(&manifestPushOpts.creds, "creds", "", "use `[username[:password]]` for accessing the registry") flags.StringVar(&manifestPushOpts.digestfile, "digestfile", "", "after copying the image, write the digest of the resulting digest to the file") flags.BoolVarP(&manifestPushOpts.forceCompressionFormat, "force-compression", "", false, "use the specified compression algorithm if the destination contains a differently-compressed variant already") flags.StringVar(&manifestPushOpts.compressionFormat, "compression-format", "", "compression format to use") flags.IntVar(&manifestPushOpts.compressionLevel, "compression-level", 0, "compression level to use") flags.StringVarP(&manifestPushOpts.format, "format", "f", "", "manifest type (oci or v2s2) to attempt to use when pushing the manifest list (default is manifest type of source)") flags.StringSliceVar(&manifestPushOpts.addCompression, "add-compression", nil, "add instances with selected compression while pushing") flags.BoolVarP(&manifestPushOpts.removeSignatures, "remove-signatures", "", false, "don't copy signatures when pushing images") flags.StringVar(&manifestPushOpts.signBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`") flags.StringVar(&manifestPushOpts.signaturePolicy, "signature-policy", "", "`pathname` of signature policy file (not usually used)") if err := flags.MarkHidden("signature-policy"); err != nil { panic(fmt.Sprintf("error marking signature-policy as hidden: %v", err)) } flags.BoolVar(&manifestPushOpts.insecure, "insecure", false, "neither require HTTPS nor verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.") if err := flags.MarkHidden("insecure"); err != nil { panic(fmt.Sprintf("error marking insecure as hidden: %v", err)) } flags.BoolVar(&manifestPushOpts.tlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.") flags.BoolVarP(&manifestPushOpts.quiet, "quiet", "q", false, "don't output progress information when pushing lists") flags.SetNormalizeFunc(cli.AliasFlags) manifestCommand.AddCommand(manifestPushCommand) manifestRmCommand := &cobra.Command{ Use: "rm", Short: "Remove manifest list or image index", Long: manifestRmDescription, RunE: func(cmd *cobra.Command, args []string) error { return manifestRmCmd(cmd, args) }, Example: `buildah manifest rm mylist:v1.11`, Args: cobra.MinimumNArgs(1), } manifestRmCommand.SetUsageTemplate(UsageTemplate()) manifestCommand.AddCommand(manifestRmCommand) } func manifestExistsCmd(c *cobra.Command, args []string) error { if len(args) == 0 { return errors.New("At least a name must be specified for the list") } name := args[0] store, err := getStore(c) if err != nil { return err } systemContext, err := parse.SystemContextFromOptions(c) if err != nil { return fmt.Errorf("building system context: %w", err) } runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) if err != nil { return err } _, err = runtime.LookupManifestList(name) if err != nil { if errors.Is(err, storage.ErrImageUnknown) { exitCode = 1 } else { return err } } return nil } func manifestCreateCmd(c *cobra.Command, args []string, opts manifestCreateOpts) error { if len(args) == 0 { return errors.New("At least a name must be specified for the list") } listImageSpec := args[0] imageSpecs := args[1:] store, err := getStore(c) if err != nil { return err } systemContext, err := parse.SystemContextFromOptions(c) if err != nil { return fmt.Errorf("building system context: %w", err) } runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) if err != nil { return err } list := manifests.Create() var manifestListID string names, err := util.ExpandNames([]string{listImageSpec}, systemContext, store) if err != nil { return fmt.Errorf("encountered while expanding image name %q: %w", listImageSpec, err) } if manifestListID, err = list.SaveToImage(store, "", names, manifest.DockerV2ListMediaType); err != nil { if errors.Is(err, storage.ErrDuplicateName) && opts.amend { for _, name := range names { manifestList, err := runtime.LookupManifestList(listImageSpec) if err != nil { logrus.Debugf("no list named %q found: %v", listImageSpec, err) continue } if _, list, err = manifests.LoadFromImage(store, manifestList.ID()); err != nil { logrus.Debugf("no list found in %q", name) continue } manifestListID = manifestList.ID() break } if list == nil { return fmt.Errorf("--amend specified but no matching manifest list found with name %q", listImageSpec) } } else { return err } } for _, imageSpec := range imageSpecs { ref, err := alltransports.ParseImageName(imageSpec) if err != nil { if ref, err = alltransports.ParseImageName(util.DefaultTransport + imageSpec); err != nil { // check if the local image exists if ref, _, err = util.FindImage(store, "", systemContext, imageSpec); err != nil { return err } } } refLocal, _, err := util.FindImage(store, "", systemContext, imageSpec) if err == nil { // Found local image so use that. ref = refLocal } _, err = list.Add(getContext(), systemContext, ref, opts.all) if err != nil { return err } } imageID, err := list.SaveToImage(store, manifestListID, names, manifest.DockerV2ListMediaType) if err == nil { fmt.Printf("%s\n", imageID) } return err } func manifestAddCmd(c *cobra.Command, args []string, opts manifestAddOpts) error { if err := auth.CheckAuthFile(opts.authfile); err != nil { return err } listImageSpec := "" imageSpec := "" switch len(args) { case 0, 1: return errors.New("At least a list image and an image to add must be specified") case 2: listImageSpec = args[0] if listImageSpec == "" { return fmt.Errorf(`Invalid image name "%s"`, args[0]) } imageSpec = args[1] if imageSpec == "" { return fmt.Errorf(`Invalid image name "%s"`, args[1]) } default: return errors.New("At least two arguments are necessary: list and image to add to list") } store, err := getStore(c) if err != nil { return err } systemContext, err := parse.SystemContextFromOptions(c) if err != nil { return fmt.Errorf("building system context: %w", err) } runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) if err != nil { return err } manifestList, err := runtime.LookupManifestList(listImageSpec) if err != nil { return err } _, list, err := manifests.LoadFromImage(store, manifestList.ID()) if err != nil { return err } ref, err := alltransports.ParseImageName(imageSpec) if err != nil { if ref, err = alltransports.ParseImageName(util.DefaultTransport + imageSpec); err != nil { // check if the local image exists if ref, _, err = util.FindImage(store, "", systemContext, imageSpec); err != nil { return err } } } digest, err := list.Add(getContext(), systemContext, ref, opts.all) if err != nil { var storeErr error // Retry without a custom system context. A user may want to add // a custom platform (see #3511). if ref, _, storeErr = util.FindImage(store, "", nil, imageSpec); storeErr != nil { logrus.Errorf("Error while trying to find image on local storage: %v", storeErr) return err } digest, storeErr = list.Add(getContext(), systemContext, ref, opts.all) if storeErr != nil { logrus.Errorf("Error while trying to add on manifest list: %v", storeErr) return err } } if opts.os != "" { if err := list.SetOS(digest, opts.os); err != nil { return err } } if opts.osVersion != "" { if err := list.SetOSVersion(digest, opts.osVersion); err != nil { return err } } if len(opts.osFeatures) != 0 { if err := list.SetOSFeatures(digest, opts.osFeatures); err != nil { return err } } if opts.arch != "" { if err := list.SetArchitecture(digest, opts.arch); err != nil { return err } } if opts.variant != "" { if err := list.SetVariant(digest, opts.variant); err != nil { return err } } if len(opts.features) != 0 { if err := list.SetFeatures(digest, opts.features); err != nil { return err } } if len(opts.annotations) != 0 { annotations := make(map[string]string) for _, annotationSpec := range opts.annotations { spec := strings.SplitN(annotationSpec, "=", 2) if len(spec) != 2 { return fmt.Errorf("no value given for annotation %q", spec[0]) } annotations[spec[0]] = spec[1] } if err := list.SetAnnotations(&digest, annotations); err != nil { return err } } updatedListID, err := list.SaveToImage(store, manifestList.ID(), nil, "") if err == nil { fmt.Printf("%s: %s\n", updatedListID, digest.String()) } return err } func manifestRemoveCmd(c *cobra.Command, args []string, opts manifestRemoveOpts) error { listImageSpec := "" var instanceDigest digest.Digest switch len(args) { case 0, 1: return errors.New("At least a list image and one or more instance digests must be specified") case 2: listImageSpec = args[0] if listImageSpec == "" { return fmt.Errorf(`Invalid image name "%s"`, args[0]) } instanceSpec := args[1] if instanceSpec == "" { return fmt.Errorf(`Invalid instance "%s"`, args[1]) } d, err := digest.Parse(instanceSpec) if err != nil { return fmt.Errorf(`Invalid instance "%s": %v`, args[1], err) } instanceDigest = d default: return errors.New("At least two arguments are necessary: list and digest of instance to remove from list") } store, err := getStore(c) if err != nil { return err } systemContext, err := parse.SystemContextFromOptions(c) if err != nil { return fmt.Errorf("building system context: %w", err) } runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) if err != nil { return err } manifestList, err := runtime.LookupManifestList(listImageSpec) if err != nil { return err } if err := manifestList.RemoveInstance(instanceDigest); err != nil { return err } fmt.Printf("%s: %s\n", manifestList.ID(), instanceDigest.String()) return nil } func manifestRmCmd(c *cobra.Command, args []string) error { store, err := getStore(c) if err != nil { return err } systemContext, err := parse.SystemContextFromOptions(c) if err != nil { return fmt.Errorf("building system context: %w", err) } runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) if err != nil { return err } options := &libimage.RemoveImagesOptions{ Filters: []string{"readonly=false"}, LookupManifest: true, } rmiReports, rmiErrors := runtime.RemoveImages(context.Background(), args, options) for _, r := range rmiReports { for _, u := range r.Untagged { fmt.Printf("untagged: %s\n", u) } } for _, r := range rmiReports { if r.Removed { fmt.Printf("%s\n", r.ID) } } var multiE *multierror.Error multiE = multierror.Append(multiE, rmiErrors...) return multiE.ErrorOrNil() } func manifestAnnotateCmd(c *cobra.Command, args []string, opts manifestAnnotateOpts) error { listImageSpec := "" imageSpec := "" switch len(args) { case 0: return errors.New("At least a list image must be specified") case 1: listImageSpec = args[0] if listImageSpec == "" { return fmt.Errorf(`Invalid image name "%s"`, args[0]) } case 2: listImageSpec = args[0] if listImageSpec == "" { return fmt.Errorf(`Invalid image name "%s"`, args[0]) } imageSpec = args[1] if imageSpec == "" { return fmt.Errorf(`Invalid image name "%s"`, args[1]) } default: return errors.New("At least two arguments are necessary: list and image to add to list") } store, err := getStore(c) if err != nil { return err } systemContext, err := parse.SystemContextFromOptions(c) if err != nil { return fmt.Errorf("building system context: %w", err) } runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) if err != nil { return err } manifestList, err := runtime.LookupManifestList(listImageSpec) if err != nil { return err } _, list, err := manifests.LoadFromImage(store, manifestList.ID()) if err != nil { return err } digest, err := digest.Parse(imageSpec) if err != nil { ctx := getContext() ref, _, err := util.FindImage(store, "", systemContext, imageSpec) if err != nil { return err } img, err := ref.NewImageSource(ctx, systemContext) if err != nil { return err } defer img.Close() manifestBytes, _, err := img.GetManifest(ctx, nil) if err != nil { return err } digest, err = manifest.Digest(manifestBytes) if err != nil { return err } } if opts.os != "" { if err := list.SetOS(digest, opts.os); err != nil { return err } } if opts.osVersion != "" { if err := list.SetOSVersion(digest, opts.osVersion); err != nil { return err } } if len(opts.osFeatures) != 0 { if err := list.SetOSFeatures(digest, opts.osFeatures); err != nil { return err } } if opts.arch != "" { if err := list.SetArchitecture(digest, opts.arch); err != nil { return err } } if opts.variant != "" { if err := list.SetVariant(digest, opts.variant); err != nil { return err } } if len(opts.features) != 0 { if err := list.SetFeatures(digest, opts.features); err != nil { return err } } if len(opts.annotations) != 0 { annotations := make(map[string]string) for _, annotationSpec := range opts.annotations { spec := strings.SplitN(annotationSpec, "=", 2) if len(spec) != 2 { return fmt.Errorf("no value given for annotation %q", spec[0]) } annotations[spec[0]] = spec[1] } if err := list.SetAnnotations(&digest, annotations); err != nil { return err } } updatedListID, err := list.SaveToImage(store, manifestList.ID(), nil, "") if err == nil { fmt.Printf("%s: %s\n", updatedListID, digest.String()) } return nil } func manifestInspectCmd(c *cobra.Command, args []string, opts manifestInspectOpts) error { if c.Flag("authfile").Changed { if err := auth.CheckAuthFile(opts.authfile); err != nil { return err } } imageSpec := "" switch len(args) { case 0: return errors.New("At least a source list ID must be specified") case 1: imageSpec = args[0] if imageSpec == "" { return fmt.Errorf(`Invalid image name "%s"`, imageSpec) } default: return errors.New("Only one argument is necessary for inspect: an image name") } store, err := getStore(c) if err != nil { return err } systemContext, err := parse.SystemContextFromOptions(c) if err != nil { return fmt.Errorf("building system context: %w", err) } return manifestInspect(getContext(), store, systemContext, imageSpec) } func manifestInspect(ctx context.Context, store storage.Store, systemContext *types.SystemContext, imageSpec string) error { runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) if err != nil { return err } printManifest := func(manifest []byte) error { var b bytes.Buffer err = json.Indent(&b, manifest, "", " ") if err != nil { return fmt.Errorf("rendering manifest for display: %w", err) } fmt.Printf("%s\n", b.String()) return nil } // Before doing a remote lookup, attempt to resolve the manifest list // locally. manifestList, err := runtime.LookupManifestList(imageSpec) if err == nil { schema2List, err := manifestList.Inspect() if err != nil { return err } rawSchema2List, err := json.Marshal(schema2List) if err != nil { return err } return printManifest(rawSchema2List) } if !errors.Is(err, storage.ErrImageUnknown) && !errors.Is(err, libimage.ErrNotAManifestList) { return err } // TODO: at some point `libimage` should support resolving manifests // like that. Similar to `libimage.Runtime.LookupImage` we could // implement a `*.LookupImageIndex`. refs, err := util.ResolveNameToReferences(store, systemContext, imageSpec) if err != nil { logrus.Debugf("error parsing reference to image %q: %v", imageSpec, err) } if ref, _, err := util.FindImage(store, "", systemContext, imageSpec); err == nil { refs = append(refs, ref) } else if ref, err := alltransports.ParseImageName(imageSpec); err == nil { refs = append(refs, ref) } if len(refs) == 0 { return fmt.Errorf("locating images with names %v", imageSpec) } var ( latestErr error result []byte ) appendErr := func(e error) { if latestErr == nil { latestErr = e } else { latestErr = fmt.Errorf("tried %v: %w", e, latestErr) } } for _, ref := range refs { logrus.Debugf("Testing reference %q for possible manifest", transports.ImageName(ref)) src, err := ref.NewImageSource(ctx, systemContext) if err != nil { appendErr(fmt.Errorf("reading image %q: %w", transports.ImageName(ref), err)) continue } defer src.Close() manifestBytes, manifestType, err := src.GetManifest(ctx, nil) if err != nil { appendErr(fmt.Errorf("loading manifest %q: %w", transports.ImageName(ref), err)) continue } if !manifest.MIMETypeIsMultiImage(manifestType) { appendErr(fmt.Errorf("manifest is of type %s (not a list type)", manifestType)) continue } result = manifestBytes break } if len(result) == 0 && latestErr != nil { return latestErr } return printManifest(result) } func manifestPushCmd(c *cobra.Command, args []string, opts pushOptions) error { if err := auth.CheckAuthFile(opts.authfile); err != nil { return err } listImageSpec := "" destSpec := "" switch len(args) { case 0: return errors.New("At least a source list ID must be specified") case 1: listImageSpec = args[0] destSpec = "docker://" + listImageSpec case 2: listImageSpec = args[0] destSpec = args[1] default: return errors.New("Only two arguments are necessary to push: source and destination") } if listImageSpec == "" { return fmt.Errorf(`invalid image name "%s"`, listImageSpec) } if destSpec == "" { return fmt.Errorf(`invalid image name "%s"`, destSpec) } store, err := getStore(c) if err != nil { return err } systemContext, err := parse.SystemContextFromOptions(c) if err != nil { return fmt.Errorf("building system context: %w", err) } if opts.compressionFormat != "" { algo, err := compression.AlgorithmByName(opts.compressionFormat) if err != nil { return err } systemContext.CompressionFormat = &algo } if c.Flag("compression-level").Changed { systemContext.CompressionLevel = &opts.compressionLevel } if c.Flag("compression-format").Changed { if !c.Flag("force-compression").Changed { // If `compression-format` is set and no value for `--force-compression` // is selected then defaults to `true`. opts.forceCompressionFormat = true } } return manifestPush(systemContext, store, listImageSpec, destSpec, opts) } func manifestPush(systemContext *types.SystemContext, store storage.Store, listImageSpec, destSpec string, opts pushOptions) error { runtime, err := libimage.RuntimeFromStore(store, &libimage.RuntimeOptions{SystemContext: systemContext}) if err != nil { return err } manifestList, err := runtime.LookupManifestList(listImageSpec) if err != nil { return err } _, list, err := manifests.LoadFromImage(store, manifestList.ID()) if err != nil { return err } dest, err := alltransports.ParseImageName(destSpec) if err != nil { return err } var manifestType string if opts.format != "" { switch opts.format { case "oci": manifestType = imgspecv1.MediaTypeImageManifest case "v2s2", "docker": manifestType = manifest.DockerV2Schema2MediaType default: return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci' or 'v2s2'", opts.format) } } options := manifests.PushOptions{ Store: store, SystemContext: systemContext, ImageListSelection: cp.CopySpecificImages, Instances: nil, RemoveSignatures: opts.removeSignatures, SignBy: opts.signBy, ManifestType: manifestType, AddCompression: opts.addCompression, ForceCompressionFormat: opts.forceCompressionFormat, } if opts.all { options.ImageListSelection = cp.CopyAllImages } if !opts.quiet { options.ReportWriter = os.Stderr } _, digest, err := list.Push(getContext(), dest, options) if err == nil && opts.rm { _, err = store.DeleteImage(manifestList.ID(), true) } if opts.digestfile != "" { if err = os.WriteFile(opts.digestfile, []byte(digest.String()), 0644); err != nil { return util.GetFailureCause(err, fmt.Errorf("failed to write digest to file %q: %w", opts.digestfile, err)) } } return err }