From a0e981f578ab4a3eedec52db3ed482d21044bb89 Mon Sep 17 00:00:00 2001 From: Nalin Dahyabhai Date: Tue, 10 Sep 2019 18:21:07 -0400 Subject: [PATCH] buildah: add a "manifest" command Add an initial version of a "manifest" command. Signed-off-by: Nalin Dahyabhai --- cmd/buildah/common.go | 37 +- cmd/buildah/common_test.go | 2 +- cmd/buildah/images.go | 2 +- cmd/buildah/manifest.go | 662 ++++++++++++++++++++++++++++++ cmd/buildah/pull.go | 11 + docs/buildah-manifest-add.md | 85 ++++ docs/buildah-manifest-annotate.md | 66 +++ docs/buildah-manifest-create.md | 50 +++ docs/buildah-manifest-inspect.md | 26 ++ docs/buildah-manifest-push.md | 61 +++ docs/buildah-manifest-remove.md | 27 ++ docs/buildah-manifest.md | 30 ++ pkg/cli/common.go | 11 + pkg/parse/parse.go | 6 + tests/lists.bats | 97 +++++ tests/loglevel.bats | 0 16 files changed, 1161 insertions(+), 12 deletions(-) create mode 100644 cmd/buildah/manifest.go create mode 100644 docs/buildah-manifest-add.md create mode 100644 docs/buildah-manifest-annotate.md create mode 100644 docs/buildah-manifest-create.md create mode 100644 docs/buildah-manifest-inspect.md create mode 100644 docs/buildah-manifest-push.md create mode 100644 docs/buildah-manifest-remove.md create mode 100644 docs/buildah-manifest.md create mode 100644 tests/lists.bats mode change 100755 => 100644 tests/loglevel.bats diff --git a/cmd/buildah/common.go b/cmd/buildah/common.go index 532b5bfe8fe..251481944b6 100644 --- a/cmd/buildah/common.go +++ b/cmd/buildah/common.go @@ -9,6 +9,7 @@ import ( "github.com/containers/buildah" "github.com/containers/buildah/pkg/umask" "github.com/containers/buildah/pkg/unshare" + "github.com/containers/image/v4/image" is "github.com/containers/image/v4/storage" "github.com/containers/image/v4/types" "github.com/containers/storage" @@ -152,37 +153,38 @@ func openImage(ctx context.Context, sc *types.SystemContext, store storage.Store return builder, nil } -func getDateAndDigestAndSize(ctx context.Context, store storage.Store, image storage.Image) (time.Time, string, int64, error) { +func getDateAndDigestAndSize(ctx context.Context, sys *types.SystemContext, store storage.Store, storeImage storage.Image) (time.Time, string, int64, error) { created := time.Time{} is.Transport.SetStore(store) - storeRef, err := is.Transport.ParseStoreReference(store, image.ID) + storeRef, err := is.Transport.ParseStoreReference(store, storeImage.ID) if err != nil { return created, "", -1, err } - img, err := storeRef.NewImage(ctx, nil) + img, err := storeRef.NewImageSource(ctx, nil) if err != nil { return created, "", -1, err } defer img.Close() - imgSize, sizeErr := img.Size() + imgSize, sizeErr := store.ImageSize(storeImage.ID) if sizeErr != nil { imgSize = -1 } - manifest, _, manifestErr := img.Manifest(ctx) + manifest, _, manifestErr := img.GetManifest(ctx, nil) manifestDigest := "" if manifestErr == nil && len(manifest) > 0 { manifestDigest = digest.Canonical.FromBytes(manifest).String() } - inspectInfo, inspectErr := img.Inspect(ctx) - if inspectErr == nil && inspectInfo != nil { - created = *inspectInfo.Created + inspectable, inspectableErr := image.FromUnparsedImage(ctx, sys, image.UnparsedInstance(img, nil)) + if inspectableErr == nil && inspectable != nil { + inspectInfo, inspectErr := inspectable.Inspect(ctx) + if inspectErr == nil && inspectInfo != nil { + created = *inspectInfo.Created + } } if sizeErr != nil { err = sizeErr } else if manifestErr != nil { err = manifestErr - } else if inspectErr != nil { - err = inspectErr } return created, manifestDigest, imgSize, err } @@ -224,6 +226,9 @@ func getImageConfig(ctx context.Context, sc *types.SystemContext, store storage. } image, err := ref.NewImage(ctx, sc) if err != nil { + if img, err2 := store.Image(imageID); err2 == nil && img.ID == imageID { + return nil, nil + } return nil, errors.Wrapf(err, "unable to open image %q", imageID) } config, err := image.OCIConfig(ctx) @@ -283,6 +288,9 @@ func getParent(ctx context.Context, sc *types.SystemContext, store storage.Store if err != nil { return nil, errors.Wrapf(err, "unable to read configuration from image %q", child.ID) } + if childConfig == nil { + return nil, nil + } for _, parent := range images { if parent.ID == child.ID { continue @@ -294,6 +302,9 @@ func getParent(ctx context.Context, sc *types.SystemContext, store storage.Store if err != nil { return nil, errors.Wrapf(err, "unable to read configuration from image %q", parent.ID) } + if parentConfig == nil { + continue + } if len(parentConfig.History)+1 != len(childConfig.History) { continue } @@ -331,6 +342,9 @@ func getChildren(ctx context.Context, sc *types.SystemContext, store storage.Sto if err != nil { return nil, errors.Wrapf(err, "unable to read configuration from image %q", parent.ID) } + if parentConfig == nil { + return nil, nil + } for _, child := range images { if child.ID == parent.ID { continue @@ -349,6 +363,9 @@ func getChildren(ctx context.Context, sc *types.SystemContext, store storage.Sto if err != nil { return nil, errors.Wrapf(err, "unable to read configuration from image %q", child.ID) } + if childConfig == nil { + continue + } if len(parentConfig.History)+1 != len(childConfig.History) { continue } diff --git a/cmd/buildah/common_test.go b/cmd/buildah/common_test.go index c50de5f2f47..57d76eef8e1 100644 --- a/cmd/buildah/common_test.go +++ b/cmd/buildah/common_test.go @@ -92,7 +92,7 @@ func TestGetSize(t *testing.T) { t.Fatalf("Error reading images: %v", err) } - _, _, _, err = getDateAndDigestAndSize(getContext(), store, images[0]) + _, _, _, err = getDateAndDigestAndSize(getContext(), &testSystemContext, store, images[0]) if err != nil { t.Error(err) } diff --git a/cmd/buildah/images.go b/cmd/buildah/images.go index 93d579a9ccd..e2b54275ea8 100644 --- a/cmd/buildah/images.go +++ b/cmd/buildah/images.go @@ -271,7 +271,7 @@ func outputImages(ctx context.Context, systemContext *types.SystemContext, store opts.readOnly = true } createdTime := image.Created - inspectedTime, digest, size, _ := getDateAndDigestAndSize(ctx, store, image) + inspectedTime, digest, size, _ := getDateAndDigestAndSize(ctx, systemContext, store, image) if !inspectedTime.IsZero() { if createdTime != inspectedTime { logrus.Debugf("image record and configuration disagree on the image's creation time for %q, using the configuration creation time: %s", image.ID, inspectedTime) diff --git a/cmd/buildah/manifest.go b/cmd/buildah/manifest.go new file mode 100644 index 00000000000..4e3a99b8081 --- /dev/null +++ b/cmd/buildah/manifest.go @@ -0,0 +1,662 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/containers/buildah/manifests" + buildahcli "github.com/containers/buildah/pkg/cli" + "github.com/containers/buildah/pkg/parse" + "github.com/containers/buildah/util" + cp "github.com/containers/image/v4/copy" + "github.com/containers/image/v4/manifest" + "github.com/containers/image/v4/transports" + "github.com/containers/image/v4/transports/alltransports" + digest "github.com/opencontainers/go-digest" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +type manifestCreateOpts = struct { + osOverride, archOverride string + all bool +} +type manifestAddOpts = struct { + osOverride, archOverride string + os, arch, variant, osVersion string + features, osFeatures, annotations []string + all bool +} +type manifestRemoveOpts = struct{} +type manifestAnnotateOpts = struct { + os, arch, variant, osVersion string + features, osFeatures, annotations []string +} +type manifestInspectOpts = struct{} +type manifestPushOpts = struct { + purge, quiet, all, tlsVerify bool + authfile, certDir, creds, digestfile, signaturePolicy string +} + +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." + manifestCreateOpts manifestCreateOpts + manifestAddOpts manifestAddOpts + manifestRemoveOpts manifestRemoveOpts + manifestAnnotateOpts manifestAnnotateOpts + manifestInspectOpts manifestInspectOpts + manifestPushOpts manifestPushOpts + ) + 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 remove localhost/list sha256:entryManifestDigest + buildah manifest inspect localhost/list + buildah manifest push localhost/list transport:destination`, + } + 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.StringVar(&manifestCreateOpts.osOverride, "override-os", "", "if any of the specified images is a list, choose the one for `os`") + if err := flags.MarkHidden("override-os"); err != nil { + panic(fmt.Sprintf("error marking override-os as hidden: %v", err)) + } + flags.StringVar(&manifestCreateOpts.archOverride, "override-arch", "", "if any of the specified images is a list, choose the one for `arch`") + if err := flags.MarkHidden("override-arch"); err != nil { + panic(fmt.Sprintf("error marking override-arch as hidden: %v", err)) + } + 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.osOverride, "override-os", "", "if any of the specified images is a list, choose the one for `os`") + if err := flags.MarkHidden("override-os"); err != nil { + panic(fmt.Sprintf("error marking override-os as hidden: %v", err)) + } + flags.StringVar(&manifestAddOpts.archOverride, "override-arch", "", "if any of the specified images is a list, choose the one for `arch`") + if err := flags.MarkHidden("override-arch"); err != nil { + panic(fmt.Sprintf("error marking override-arch as hidden: %v", err)) + } + 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.all, "all", false, "add all of the list's images if the image is a list") + 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) + + 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), + } + 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(2), + } + manifestPushCommand.SetUsageTemplate(UsageTemplate()) + flags = manifestPushCommand.Flags() + flags.BoolVar(&manifestPushOpts.purge, "purge", 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", buildahcli.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.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.tlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry") + flags.BoolVarP(&manifestPushOpts.quiet, "quiet", "q", false, "don't output progress information when pushing lists") + manifestCommand.AddCommand(manifestPushCommand) +} + +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 errors.Wrapf(err, "error building system context") + } + + list := manifests.Create() + + names, err := util.ExpandNames([]string{listImageSpec}, "", systemContext, store) + if err != nil { + return errors.Wrapf(err, "error encountered while expanding image name %q", listImageSpec) + } + + for _, imageSpec := range imageSpecs { + ref, _, err := util.FindImage(store, "", systemContext, imageSpec) + if err != nil { + if ref, err = alltransports.ParseImageName(imageSpec); err != nil { + return err + } + } + if _, err = list.Add(getContext(), systemContext, ref, opts.all); err != nil { + return err + } + } + + imageID, err := list.SaveToImage(store, "", names, manifest.DockerV2ListMediaType) + if err == nil { + fmt.Printf("%s\n", imageID) + } + return err +} + +func manifestAddCmd(c *cobra.Command, args []string, opts manifestAddOpts) error { + 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 errors.Errorf(`Invalid image name "%s"`, args[0]) + } + imageSpec = args[1] + if imageSpec == "" { + return errors.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 errors.Wrapf(err, "error building system context") + } + + _, listImage, err := util.FindImage(store, "", systemContext, listImageSpec) + if err != nil { + return err + } + + ref, _, err := util.FindImage(store, "", systemContext, imageSpec) + if err != nil { + if ref, err = alltransports.ParseImageName(imageSpec); err != nil { + return err + } + } + + _, list, err := manifests.LoadFromImage(store, listImage.ID) + if err != nil { + return err + } + + ctx := getContext() + + digest, err := list.Add(ctx, systemContext, ref, opts.all) + 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 errors.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, listImage.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 errors.Errorf(`Invalid image name "%s"`, args[0]) + } + instanceSpec := args[1] + if instanceSpec == "" { + return errors.Errorf(`Invalid instance "%s"`, args[1]) + } + d, err := digest.Parse(instanceSpec) + if err != nil { + return errors.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 errors.Wrapf(err, "error building system context") + } + + _, listImage, err := util.FindImage(store, "", systemContext, listImageSpec) + if err != nil { + return err + } + + _, list, err := manifests.LoadFromImage(store, listImage.ID) + if err != nil { + return err + } + + err = list.Remove(instanceDigest) + if err != nil { + return err + } + + updatedListID, err := list.SaveToImage(store, listImage.ID, nil, "") + if err == nil { + fmt.Printf("%s: %s\n", updatedListID, instanceDigest.String()) + } + + return nil +} + +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 errors.Errorf(`Invalid image name "%s"`, args[0]) + } + case 2: + listImageSpec = args[0] + if listImageSpec == "" { + return errors.Errorf(`Invalid image name "%s"`, args[0]) + } + imageSpec = args[1] + if imageSpec == "" { + return errors.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 errors.Wrapf(err, "error building system context") + } + + _, listImage, err := util.FindImage(store, "", systemContext, listImageSpec) + if err != nil { + return err + } + + _, list, err := manifests.LoadFromImage(store, listImage.ID) + if err != nil { + return err + } + + ctx := getContext() + + digest, err := digest.Parse(imageSpec) + if err != nil { + 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 errors.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, listImage.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 { + 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 errors.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 errors.Wrapf(err, "error building system context") + } + + ref, _, err := util.FindImage(store, "", systemContext, imageSpec) + if err != nil { + if ref, err = alltransports.ParseImageName(imageSpec); err != nil { + return err + } + } + + ctx := getContext() + + src, err := ref.NewImageSource(ctx, systemContext) + if err != nil { + return errors.Wrapf(err, "error reading image %q", transports.ImageName(ref)) + } + defer src.Close() + + manifestBytes, manifestType, err := src.GetManifest(ctx, nil) + if err != nil { + return errors.Wrapf(err, "error loading manifest from image %q", transports.ImageName(ref)) + } + if !manifest.MIMETypeIsMultiImage(manifestType) { + return errors.Errorf("manifest from image %q is of type %q, which is not a list type", transports.ImageName(ref), manifestType) + } + + var b bytes.Buffer + err = json.Indent(&b, manifestBytes, "", " ") + if err != nil { + return errors.Wrapf(err, "error rendering manifest for display") + } + + fmt.Printf("%s\n", b.String()) + + return nil +} + +func manifestPushCmd(c *cobra.Command, args []string, opts manifestPushOpts) error { + listImageSpec := "" + destSpec := "" + switch len(args) { + case 0: + return errors.New("At least a source list ID must be specified") + case 1: + return errors.New("Two arguments are necessary to push: source and destination") + case 2: + listImageSpec = args[0] + destSpec = args[1] + if listImageSpec == "" { + return errors.Errorf(`Invalid image name "%s"`, listImageSpec) + } + if destSpec == "" { + return errors.Errorf(`Invalid image name "%s"`, destSpec) + } + default: + return errors.New("Only two arguments are necessary to push: source and destination") + } + + store, err := getStore(c) + if err != nil { + return err + } + + systemContext, err := parse.SystemContextFromOptions(c) + if err != nil { + return errors.Wrapf(err, "error building system context") + } + + _, listImage, err := util.FindImage(store, "", systemContext, listImageSpec) + if err != nil { + return err + } + + _, list, err := manifests.LoadFromImage(store, listImage.ID) + if err != nil { + return err + } + + ctx := getContext() + + dest, err := alltransports.ParseImageName(destSpec) + if err != nil { + return err + } + + options := manifests.PushOptions{ + Store: store, + SystemContext: systemContext, + ImageListSelection: cp.CopySpecificImages, + Instances: nil, + } + if opts.all { + options.ImageListSelection = cp.CopyAllImages + } + if !opts.quiet { + options.ReportWriter = os.Stderr + } + + _, digest, err := list.Push(ctx, dest, options) + + if err == nil && opts.purge { + _, err = store.DeleteImage(listImage.ID, true) + } + + if opts.digestfile != "" { + if err = ioutil.WriteFile(opts.digestfile, []byte(digest.String()), 0644); err != nil { + return util.GetFailureCause(err, errors.Wrapf(err, "failed to write digest to file %q", opts.digestfile)) + } + } + + return err +} diff --git a/cmd/buildah/pull.go b/cmd/buildah/pull.go index 5a2327008b1..6caf9f11adb 100644 --- a/cmd/buildah/pull.go +++ b/cmd/buildah/pull.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "runtime" "github.com/containers/buildah" buildahcli "github.com/containers/buildah/pkg/cli" @@ -17,6 +18,8 @@ type pullResults struct { blobCache string certDir string creds string + overrideArch string + overrideOS string signaturePolicy string quiet bool tlsVerify bool @@ -56,6 +59,14 @@ func init() { panic(fmt.Sprintf("error marking signature-policy as hidden: %v", err)) } flags.BoolVarP(&opts.quiet, "quiet", "q", false, "don't output progress information when pulling images") + flags.StringVar(&opts.overrideOS, "override-os", runtime.GOOS, "prefer `OS` instead of the running OS for choosing images") + if err := flags.MarkHidden("override-os"); err != nil { + panic(fmt.Sprintf("error marking override-os as hidden: %v", err)) + } + flags.StringVar(&opts.overrideArch, "override-arch", runtime.GOARCH, "prefer `ARCH` instead of the architecture of the machine for choosing images") + if err := flags.MarkHidden("override-arch"); err != nil { + panic(fmt.Sprintf("error marking override-arch as hidden: %v", err)) + } flags.BoolVar(&opts.tlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry") if err := flags.MarkHidden("blob-cache"); err != nil { panic(fmt.Sprintf("error marking blob-cache as hidden: %v", err)) diff --git a/docs/buildah-manifest-add.md b/docs/buildah-manifest-add.md new file mode 100644 index 00000000000..2b55b631058 --- /dev/null +++ b/docs/buildah-manifest-add.md @@ -0,0 +1,85 @@ +# buildah-manifest-add "1" "September 2019" "buildah" + +## NAME + +buildah\-manifest\-add - Add an image to a manifest list or image index. + +## SYNOPSIS + +**buildah manifest add** *listNameOrIndexName* *imageName* + +## DESCRIPTION + +Adds the specified image to the specified manifest list or image index. + +## RETURN VALUE + +The list image's ID and the digest of the image's manifest. + +## OPTIONS + +**--all** + +If the image which should be added to the list or index is itself a list or +index, add all of the contents to the local list. By default, only one image +from such a list or index will be added to the list or index. Combining +*--all* with any of the other options described below is NOT recommended. + +**--annotation** *annotation=value* + +Set an annotation on the entry for the newly-added image. + +**--arch** + +Override the architecture which the list or index records as a requirement for +the image. If *imageName* refers to a manifest list or image index, the +architecture information will be retrieved from it. Otherwise, it will be +retrieved from the image's configuration information. + +**--features** + +Specify the features list which the list or index records as requirements for +the image. This option is rarely used. + +**--os** + +Override the OS which the list or index records as a requirement for the image. +If *imageName* refers to a manifest list or image index, the OS information +will be retrieved from it. Otherwise, it will be retrieved from the image's +configuration information. + +**--os-features** + +Specify the OS features list which the list or index records as requirements +for the image. This option is rarely used. + +**--os-version** + +Specify the OS version which the list or index records as a requirement for the +image. This option is rarely used. + +**--variant** + +Specify the variant which the list or index records for the image. This option +is typically used to distinguish between multiple entries which share the same +architecture value, but which expect different versions of its instruction set. + +## EXAMPLE + +``` +buildah manifest add mylist:v1.11 docker://fedora +506d8f4bb54931ea03a7e70173a0ed6302e3fb92dfadb3955ba5c17812e95c51: sha256:f81f09918379d5442d20dff82a298f29698197035e737f76e511d5af422cabd7 +``` + +``` +buildah manifest add --all mylist:v1.11 docker://fedora +506d8f4bb54931ea03a7e70173a0ed6302e3fb92dfadb3955ba5c17812e95c51: sha256:f81f09918379d5442d20dff82a298f29698197035e737f76e511d5af422cabd7 +``` + +``` +buildah manifest add --arch arm64 --variant v8 mylist:v1.11 docker://fedora@sha256:c829b1810d2dbb456e74a695fd3847530c8319e5a95dca623e9f1b1b89020d8b +506d8f4bb54931ea03a7e70173a0ed6302e3fb92dfadb3955ba5c17812e95c51: sha256:c829b1810d2dbb456e74a695fd3847530c8319e5a95dca623e9f1b1b89020d8b +``` + +## SEE ALSO +buildah(1), buildah-manifest(1), buildah-manifest-create(1), buildah-manifest-remove(1), buildah-manifest-annotate(1), buildah-manifest-inspect(1), buildah-manifest-push(1), buildah-rmi(1) diff --git a/docs/buildah-manifest-annotate.md b/docs/buildah-manifest-annotate.md new file mode 100644 index 00000000000..86e934189ea --- /dev/null +++ b/docs/buildah-manifest-annotate.md @@ -0,0 +1,66 @@ +# buildah-manifest-annotate "1" "September 2019" "buildah" + +## NAME + +buildah\-manifest\-annotate - Add and update information about an image to a manifest list or image index. + +## SYNOPSIS + +**buildah manifest annotate** [options...] *listNameOrIndexName* *imageManifestDigest* + +## DESCRIPTION + +Adds or updates information about an image included in a manifest list or image index. + +## RETURN VALUE + +The list image's ID and the digest of the image's manifest. + +## OPTIONS + +**--annotation** *annotation=value* + +Set an annotation on the entry for the specified image. + +**--arch** + +Override the architecture which the list or index records as a requirement for +the image. This is usually automatically retrieved from the image's +configuration information, so it is rarely necessary to use this option. + +**--features** + +Specify the features list which the list or index records as requirements for +the image. This option is rarely used. + +**--os** + +Override the OS which the list or index records as a requirement for the image. +This is usually automatically retrieved from the image's configuration +information, so it is rarely necessary to use this option. + +**--os-features** + +Specify the OS features list which the list or index records as requirements +for the image. This option is rarely used. + +**--os-version** + +Specify the OS version which the list or index records as a requirement for the +image. This option is rarely used. + +**--variant** + +Specify the variant which the list or index records for the image. This option +is typically used to distinguish between multiple entries which share the same +architecture value, but which expect different versions of its instruction set. + +## EXAMPLE + +``` +buildah manifest annotate --arch arm64 --variant v8 mylist:v1.11 sha256:c829b1810d2dbb456e74a695fd3847530c8319e5a95dca623e9f1b1b89020d8b +506d8f4bb54931ea03a7e70173a0ed6302e3fb92dfadb3955ba5c17812e95c51: sha256:c829b1810d2dbb456e74a695fd3847530c8319e5a95dca623e9f1b1b89020d8b +``` + +## SEE ALSO +buildah(1), buildah-manifest(1), buildah-manifest-create(1), buildah-manifest-add(1), buildah-manifest-remove(1), buildah-manifest-inspect(1), buildah-manifest-push(1), buildah-rmi(1) diff --git a/docs/buildah-manifest-create.md b/docs/buildah-manifest-create.md new file mode 100644 index 00000000000..dce166dafcf --- /dev/null +++ b/docs/buildah-manifest-create.md @@ -0,0 +1,50 @@ +# buildah-manifest-create "1" "September 2019" "buildah" + +## NAME + +buildah\-manifest\-create - Create a manifest list or image index. + +## SYNOPSIS + +**buildah manifest create** *listNameOrIndexName* [*imageName* ...] + +## DESCRIPTION + +Creates a new manifest list and stores it as an image in local storage using +the specified name. + +If additional images are specified, they are added to the newly-created list or +index. + +## RETURN VALUE + +The randomly-generated image ID of the newly-created list or index. The image +can be deleted using the *buildah rmi* command. + +## OPTIONS + +**--all** + +If any of the images which should be added to the new list or index are +themselves lists or indexes, add all of their contents. By default, only one +image from such a list will be added to the newly-created list or index. + +## EXAMPLE + +``` +buildah manifest create mylist:v1.11 +941c1259e4b85bebf23580a044e4838aa3c1e627528422c9bf9262ff1661fca9 +``` + +``` +buildah manifest create mylist:v1.11 docker://fedora +941c1259e4b85bebf23580a044e4838aa3c1e627528422c9bf9262ff1661fca9 +``` + +``` +buildah manifest create --all mylist:v1.11 docker://fedora +941c1259e4b85bebf23580a044e4838aa3c1e627528422c9bf9262ff1661fca9 +``` + +## SEE ALSO +buildah(1), buildah-manifest(1), buildah-manifest-add(1), buildah-manifest-remove(1), buildah-manifest-annotate(1), buildah-manifest-inspect(1), buildah-manifest-push(1), buildah-rmi(1) diff --git a/docs/buildah-manifest-inspect.md b/docs/buildah-manifest-inspect.md new file mode 100644 index 00000000000..2f73ce96937 --- /dev/null +++ b/docs/buildah-manifest-inspect.md @@ -0,0 +1,26 @@ +# buildah-manifest-inspect "1" "September 2019" "buildah" + +## NAME + +buildah\-manifest\-inspect - Display a manifest list or image index. + +## SYNOPSIS + +**buildah manifest inspect** *listNameOrIndexName* + +## DESCRIPTION + +Displays the manifest list or image index stored using the specified image name. + +## RETURN VALUE + +A formatted JSON representation of the manifest list or image index. + +## EXAMPLE + +``` +buildah manifest inspect mylist:v1.11 +``` + +## SEE ALSO +buildah(1), buildah-manifest(1), buildah-manifest-create(1), buildah-manifest-add(1), buildah-manifest-remove(1), buildah-manifest-annotate(1), buildah-manifest-push(1), buildah-rmi(1) diff --git a/docs/buildah-manifest-push.md b/docs/buildah-manifest-push.md new file mode 100644 index 00000000000..e1f2cf1765d --- /dev/null +++ b/docs/buildah-manifest-push.md @@ -0,0 +1,61 @@ +# buildah-manifest-push "1" "September 2019" "buildah" + +## NAME + +buildah\-manifest\-push - Push a manifest list or image index to a registry. + +## SYNOPSIS + +**buildah manifest push** [options...] *listNameOrIndexName* *transport:details* + +## DESCRIPTION + +Pushes a manifest list or image index to a registry. + +## RETURN VALUE + +The list image's ID and the digest of the image's manifest. + +## OPTIONS + +**--all** + +Push the images mentioned in the manifest list or image index, in addition to +the list or index itself. + +**--authfile** *path* + +Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `buildah login`. +If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. + +**--cert-dir** *path* + +Use certificates at *path* (\*.crt, \*.cert, \*.key) to connect to the registry. +Default certificates directory is _/etc/containers/certs.d_. + +**--creds** *creds* + +The [username[:password]] to use to authenticate with the registry if required. +If one or both values are not supplied, a command line prompt will appear and the +value can be entered. The password is entered without echo. + +**--digestfile** *Digestfile* + +After copying the image, write the digest of the resulting image to the file. + +**--purge** + +Delete the manifest list or image index from local storage if pushing succeeds. + +**--tls-verify** *bool-value* + +Require HTTPS and verify certificates when talking to container registries (defaults to true) + +## EXAMPLE + +``` +buildah manifest push mylist:v1.11 docker://registry.example.org/mylist:v1.11 +``` + +## SEE ALSO +buildah(1), buildah-manifest(1), buildah-manifest-create(1), buildah-manifest-add(1), buildah-manifest-remove(1), buildah-manifest-annotate(1), buildah-manifest-inspect(1), buildah-rmi(1) diff --git a/docs/buildah-manifest-remove.md b/docs/buildah-manifest-remove.md new file mode 100644 index 00000000000..f131a33b7f6 --- /dev/null +++ b/docs/buildah-manifest-remove.md @@ -0,0 +1,27 @@ +# buildah-manifest-remove "1" "September 2019" "buildah" + +## NAME + +buildah\-manifest\-remove - Remove an image from a manifest list or image index. + +## SYNOPSIS + +**buildah manifest remove** *listNameOrIndexName* *imageManifestDigest* + +## DESCRIPTION + +Removes the image with the specified digest from the specified manifest list or image index. + +## RETURN VALUE + +The list image's ID and the digest of the removed image's manifest. + +## EXAMPLE + +``` +buildah manifest remove mylist:v1.11 sha256:f81f09918379d5442d20dff82a298f29698197035e737f76e511d5af422cabd7 +506d8f4bb54931ea03a7e70173a0ed6302e3fb92dfadb3955ba5c17812e95c51: sha256:f81f09918379d5442d20dff82a298f29698197035e737f76e511d5af422cabd7 +``` + +## SEE ALSO +buildah(1), buildah-manifest(1), buildah-manifest-create(1), buildah-manifest-add(1), buildah-manifest-annotate(1), buildah-manifest-inspect(1), buildah-manifest-push(1), buildah-rmi(1) diff --git a/docs/buildah-manifest.md b/docs/buildah-manifest.md new file mode 100644 index 00000000000..28e5b9eed33 --- /dev/null +++ b/docs/buildah-manifest.md @@ -0,0 +1,30 @@ +# buildah-manifest "1" "September 2019" "buildah" + +## NAME +buildah-manifest - Create and manipulate manifest lists and image indexes. + +## SYNOPSIS +buildah manifest COMMAND [OPTIONS] [ARG...] + +## DESCRIPTION +The `buildah manifest` command provides subcommands which can be used to: + + * Create a working Docker manifest list or OCI image index. + * Add an entry to a manifest list or image index for a specified image. + * Add or update information about an entry in a manifest list or image index. + * Delete a working container or an image. + * Push a manifest list or image index to a registry or other location. + +## SUBCOMMANDS + +| Command | Man Page | Description | +| ------- | ------------------------------------------------------------ | --------------------------------------------------------------------------- | +| create | [buildah-manifest-create(1)](buildah-manifest-create.md) | Create a manifest list or image index. | +| add | [buildah-manifest-add(1)](buildah-manifest-add.md) | Add an image to a manifest list or image index. | +| annotate | [buildah-manifest-annotate(1)](buildah-manifest-annotate.md) | Add or update information about an image in a manifest list or image index. | +| inspect | [buildah-manifest-inspect(1)](buildah-manifest-inspect.md) | Display the contents of a manifest list or image index. | +| push | [buildah-manifest-push(1)](buildah-manifest-push.md) | Push a manifest list or image index to a registry or other location. | +| remove | [buildah-manifest-remove(1)](buildah-manifest-remove.md) | Remove an image from a manifest list or image index. | + +## SEE ALSO +buildah(1), buildah-manifest-create(1), buildah-manifest-add(1), buildah-manifest-remove(1), buildah-manifest-annotate(1), buildah-manifest-inspect(1), buildah-manifest-push(1) diff --git a/pkg/cli/common.go b/pkg/cli/common.go index b979ad48b44..a9bf94a323e 100644 --- a/pkg/cli/common.go +++ b/pkg/cli/common.go @@ -8,6 +8,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "strings" "github.com/containers/buildah" @@ -95,6 +96,8 @@ type FromAndBudResults struct { Isolation string Memory string MemorySwap string + OverrideArch string + OverrideOS string SecurityOpt []string ShmSize string Ulimit []string @@ -194,6 +197,14 @@ func GetFromAndBudFlags(flags *FromAndBudResults, usernsResults *UserNSResults, fs.StringVar(&flags.Isolation, "isolation", DefaultIsolation(), "`type` of process isolation to use. Use BUILDAH_ISOLATION environment variable to override.") fs.StringVarP(&flags.Memory, "memory", "m", "", "memory limit (format: [], where unit = b, k, m or g)") fs.StringVar(&flags.MemorySwap, "memory-swap", "", "swap limit equal to memory plus swap: '-1' to enable unlimited swap") + fs.StringVar(&flags.OverrideOS, "override-os", runtime.GOOS, "prefer `OS` instead of the running OS when pulling images") + if err := fs.MarkHidden("override-os"); err != nil { + panic(fmt.Sprintf("error marking override-os as hidden: %v", err)) + } + fs.StringVar(&flags.OverrideArch, "override-arch", runtime.GOARCH, "prefer `ARCH` instead of the architecture of the machine when pulling images") + if err := fs.MarkHidden("override-arch"); err != nil { + panic(fmt.Sprintf("error marking override-arch as hidden: %v", err)) + } fs.StringArrayVar(&flags.SecurityOpt, "security-opt", []string{}, "security options (default [])") fs.StringVar(&flags.ShmSize, "shm-size", "65536k", "size of '/dev/shm'. The format is ``.") fs.StringSliceVar(&flags.Ulimit, "ulimit", []string{}, "ulimit options (default [])") diff --git a/pkg/parse/parse.go b/pkg/parse/parse.go index f0996315ae7..df092844d74 100644 --- a/pkg/parse/parse.go +++ b/pkg/parse/parse.go @@ -583,6 +583,12 @@ func SystemContextFromOptions(c *cobra.Command) (*types.SystemContext, error) { ctx.RegistriesDirPath = regConfDir } ctx.DockerRegistryUserAgent = fmt.Sprintf("Buildah/%s", buildah.Version) + if os, err := c.Flags().GetString("override-os"); err == nil { + ctx.OSChoice = os + } + if arch, err := c.Flags().GetString("override-arch"); err == nil { + ctx.ArchitectureChoice = arch + } return ctx, nil } diff --git a/tests/lists.bats b/tests/lists.bats new file mode 100644 index 00000000000..30f52c47282 --- /dev/null +++ b/tests/lists.bats @@ -0,0 +1,97 @@ +#!/usr/bin/env bats + +load helpers + +IMAGE_LIST=docker://k8s.gcr.io/pause:3.1 +IMAGE_LIST_INSTANCE=docker://k8s.gcr.io/pause@sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39 +IMAGE_LIST_AMD64_INSTANCE_DIGEST=sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610 +IMAGE_LIST_ARM_INSTANCE_DIGEST=sha256:c84b0a3a07b628bc4d62e5047d0f8dff80f7c00979e1e28a821a033ecda8fe53 +IMAGE_LIST_ARM64_INSTANCE_DIGEST=sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39 +IMAGE_LIST_PPC64LE_INSTANCE_DIGEST=sha256:bcf9771c0b505e68c65440474179592ffdfa98790eb54ffbf129969c5e429990 +IMAGE_LIST_S390X_INSTANCE_DIGEST=sha256:882a20ee0df7399a445285361d38b711c299ca093af978217112c73803546d5e + +@test "manifest-create" { + run_buildah manifest create foo +} + +@test "manifest-add" { + run_buildah manifest create foo + run_buildah manifest add foo ${IMAGE_LIST} +} + +@test "manifest-add-one" { + run_buildah manifest create foo + run_buildah manifest add --override-arch=arm64 foo ${IMAGE_LIST_INSTANCE} + run_buildah 1 inspect foo + expect_output --substring "does not exist" + run_buildah manifest inspect foo + expect_output --substring ${IMAGE_LIST_ARM64_INSTANCE_DIGEST} +} + +@test "manifest-add-all" { + run_buildah manifest create foo + run_buildah manifest add --all foo ${IMAGE_LIST} + run_buildah manifest inspect foo + expect_output --substring ${IMAGE_LIST_AMD64_INSTANCE_DIGEST} + expect_output --substring ${IMAGE_LIST_ARM_INSTANCE_DIGEST} + expect_output --substring ${IMAGE_LIST_ARM64_INSTANCE_DIGEST} + expect_output --substring ${IMAGE_LIST_PPC64LE_INSTANCE_DIGEST} + expect_output --substring ${IMAGE_LIST_S390X_INSTANCE_DIGEST} +} + +@test "manifest-remove" { + run_buildah manifest create foo + run_buildah manifest add --all foo ${IMAGE_LIST} + run_buildah manifest inspect foo + expect_output --substring ${IMAGE_LIST_ARM64_INSTANCE_DIGEST} + run_buildah manifest remove foo ${IMAGE_LIST_ARM64_INSTANCE_DIGEST} + run_buildah manifest inspect foo + expect_output --substring ${IMAGE_LIST_AMD64_INSTANCE_DIGEST} + expect_output --substring ${IMAGE_LIST_ARM_INSTANCE_DIGEST} + expect_output --substring ${IMAGE_LIST_PPC64LE_INSTANCE_DIGEST} + expect_output --substring ${IMAGE_LIST_S390X_INSTANCE_DIGEST} + run grep ${IMAGE_LIST_ARM64_INSTANCE_DIGEST} <<< "$output" + [ $status -ne 0 ] +} + +@test "manifest-remove-not-found" { + run_buildah manifest create foo + run_buildah manifest add foo ${IMAGE_LIST} + run_buildah 1 manifest remove foo sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef +} + +@test "manifest-push" { + run_buildah manifest create foo + run_buildah manifest add --all foo ${IMAGE_LIST} + run_buildah manifest push --signature-policy ${TESTSDIR}/policy.json foo dir:${TESTDIR}/pushed + case "$(go env GOARCH 2> /dev/null)" in + amd64) IMAGE_LIST_EXPECTED_INSTANCE_DIGEST=${IMAGE_LIST_AMD64_INSTANCE_DIGEST} ;; + arm64) IMAGE_LIST_EXPECTED_INSTANCE_DIGEST=${IMAGE_LIST_ARM64_INSTANCE_DIGEST} ;; + arm) IMAGE_LIST_EXPECTED_INSTANCE_DIGEST=${IMAGE_LIST_ARM_INSTANCE_DIGEST} ;; + ppc64le) IMAGE_LIST_EXPECTED_INSTANCE_DIGEST=${IMAGE_LIST_PPC64LE_INSTANCE_DIGEST} ;; + s390x) IMAGE_LIST_EXPECTED_INSTANCE_DIGEST=${IMAGE_LIST_S390X_INSTANCE_DIGEST} ;; + *) skip "current arch \"$(go env GOARCH 2> /dev/null)\" not present in manifest list" ;; + esac + run grep ${IMAGE_LIST_EXPECTED_INSTANCE_DIGEST##sha256} ${TESTDIR}/pushed/manifest.json + [ $status -eq 0 ] +} + +@test "manifest-push-all" { + run_buildah manifest create foo + run_buildah manifest add --all foo ${IMAGE_LIST} + run_buildah manifest push --signature-policy ${TESTSDIR}/policy.json --all foo dir:${TESTDIR}/pushed + run sha256sum ${TESTDIR}/pushed/* + expect_output --substring ${IMAGE_LIST_AMD64_INSTANCE_DIGEST##sha256:} + expect_output --substring ${IMAGE_LIST_ARM_INSTANCE_DIGEST##sha256:} + expect_output --substring ${IMAGE_LIST_ARM64_INSTANCE_DIGEST##sha256:} + expect_output --substring ${IMAGE_LIST_PPC64LE_INSTANCE_DIGEST##sha256:} + expect_output --substring ${IMAGE_LIST_S390X_INSTANCE_DIGEST##sha256:} +} + +@test "manifest-push-purge" { + run_buildah manifest create foo + run_buildah manifest add --override-arch=arm64 foo ${IMAGE_LIST} + run_buildah manifest inspect foo + run_buildah manifest push --signature-policy ${TESTSDIR}/policy.json --purge foo dir:${TESTDIR}/pushed + run_buildah 1 manifest inspect foo +} diff --git a/tests/loglevel.bats b/tests/loglevel.bats old mode 100755 new mode 100644