diff --git a/buildah.go b/buildah.go index 6be5f4d9f8a..d57eea818e3 100644 --- a/buildah.go +++ b/buildah.go @@ -12,7 +12,7 @@ import ( "github.com/containers/buildah/docker" "github.com/containers/buildah/util" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/containers/storage/pkg/ioutils" v1 "github.com/opencontainers/image-spec/specs-go/v1" diff --git a/cmd/buildah/commit.go b/cmd/buildah/commit.go index 2aa378e4c80..3c91211fffd 100644 --- a/cmd/buildah/commit.go +++ b/cmd/buildah/commit.go @@ -10,9 +10,9 @@ import ( buildahcli "github.com/containers/buildah/pkg/cli" "github.com/containers/buildah/pkg/parse" "github.com/containers/buildah/util" - "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/transports/alltransports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" diff --git a/cmd/buildah/common.go b/cmd/buildah/common.go index 532b5bfe8fe..b48d94ed0fd 100644 --- a/cmd/buildah/common.go +++ b/cmd/buildah/common.go @@ -9,8 +9,9 @@ import ( "github.com/containers/buildah" "github.com/containers/buildah/pkg/umask" "github.com/containers/buildah/pkg/unshare" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/image" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/types" "github.com/containers/storage" digest "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -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..3e6012336aa 100644 --- a/cmd/buildah/common_test.go +++ b/cmd/buildah/common_test.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/containers/buildah" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/types" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -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..b043dfa7358 100644 --- a/cmd/buildah/images.go +++ b/cmd/buildah/images.go @@ -11,8 +11,8 @@ import ( buildahcli "github.com/containers/buildah/pkg/cli" "github.com/containers/buildah/pkg/formats" "github.com/containers/buildah/pkg/parse" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/types" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/types" "github.com/containers/storage" units "github.com/docker/go-units" "github.com/pkg/errors" @@ -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/images_test.go b/cmd/buildah/images_test.go index ba56f607096..9d3f05da979 100644 --- a/cmd/buildah/images_test.go +++ b/cmd/buildah/images_test.go @@ -10,7 +10,7 @@ import ( "time" "github.com/containers/buildah/util" - is "github.com/containers/image/v4/storage" + is "github.com/containers/image/v5/storage" "github.com/containers/storage" ) diff --git a/cmd/buildah/login.go b/cmd/buildah/login.go index c68245067e9..66871e28ef9 100644 --- a/cmd/buildah/login.go +++ b/cmd/buildah/login.go @@ -8,9 +8,10 @@ import ( buildahcli "github.com/containers/buildah/pkg/cli" "github.com/containers/buildah/pkg/parse" - "github.com/containers/image/v4/docker" - "github.com/containers/image/v4/pkg/docker/config" + "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/pkg/docker/config" "github.com/pkg/errors" + "github.com/sirupsen/logrus" "github.com/spf13/cobra" "golang.org/x/crypto/ssh/terminal" ) @@ -116,15 +117,15 @@ func loginCmd(c *cobra.Command, args []string, iopts *loginReply) error { return err } } - switch err { - case nil: + if err == nil { fmt.Println("Login Succeeded!") return nil - case docker.ErrUnauthorizedForCredentials: + } + if unauthorized, ok := err.(docker.ErrUnauthorizedForCredentials); ok { + logrus.Debugf("error logging into %q: %v", server, unauthorized) return errors.Errorf("error logging into %q: invalid username/password", server) - default: - return errors.Wrapf(err, "error authenticating creds for %q", server) } + return errors.Wrapf(err, "error authenticating creds for %q", server) } // GetUserAndPass gets the username and password from STDIN if not given diff --git a/cmd/buildah/logout.go b/cmd/buildah/logout.go index 3b8b740d13c..9b421500e5f 100644 --- a/cmd/buildah/logout.go +++ b/cmd/buildah/logout.go @@ -5,7 +5,7 @@ import ( buildahcli "github.com/containers/buildah/pkg/cli" "github.com/containers/buildah/pkg/parse" - "github.com/containers/image/v4/pkg/docker/config" + "github.com/containers/image/v5/pkg/docker/config" "github.com/pkg/errors" "github.com/spf13/cobra" ) diff --git a/cmd/buildah/manifest.go b/cmd/buildah/manifest.go new file mode 100644 index 00000000000..aebcd668687 --- /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/v5/copy" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/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/cmd/buildah/push.go b/cmd/buildah/push.go index 790d2adfcfc..f20bef31492 100644 --- a/cmd/buildah/push.go +++ b/cmd/buildah/push.go @@ -11,9 +11,9 @@ import ( buildahcli "github.com/containers/buildah/pkg/cli" "github.com/containers/buildah/pkg/parse" "github.com/containers/buildah/util" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/transports/alltransports" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/transports/alltransports" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" diff --git a/cmd/buildah/rmi.go b/cmd/buildah/rmi.go index ce28979bb3f..ec1970426f1 100644 --- a/cmd/buildah/rmi.go +++ b/cmd/buildah/rmi.go @@ -9,10 +9,10 @@ import ( buildahcli "github.com/containers/buildah/pkg/cli" "github.com/containers/buildah/pkg/parse" "github.com/containers/buildah/util" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/transports/alltransports" - "github.com/containers/image/v4/types" + is "github.com/containers/image/v5/storage" + "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/pkg/errors" "github.com/sirupsen/logrus" diff --git a/cmd/buildah/rmi_test.go b/cmd/buildah/rmi_test.go index ae7ee7840b3..ad50d8700f7 100644 --- a/cmd/buildah/rmi_test.go +++ b/cmd/buildah/rmi_test.go @@ -4,7 +4,7 @@ import ( "strings" "testing" - is "github.com/containers/image/v4/storage" + is "github.com/containers/image/v5/storage" "github.com/containers/storage" ) diff --git a/cmd/buildah/version.go b/cmd/buildah/version.go index 1972df1850e..2b09adfe0d1 100644 --- a/cmd/buildah/version.go +++ b/cmd/buildah/version.go @@ -8,6 +8,7 @@ import ( cniversion "github.com/containernetworking/cni/pkg/version" "github.com/containers/buildah" + iversion "github.com/containers/image/v5/version" ispecs "github.com/opencontainers/image-spec/specs-go" rspecs "github.com/opencontainers/runtime-spec/specs-go" "github.com/spf13/cobra" @@ -38,6 +39,7 @@ func versionCmd(c *cobra.Command, args []string) error { fmt.Println("Runtime Spec: ", rspecs.Version) fmt.Println("CNI Spec: ", cniversion.Current()) fmt.Println("libcni Version: ", cniVersion) + fmt.Println("image Version: ", iversion.Version) fmt.Println("Git Commit: ", GitCommit) //Prints out the build time in readable format diff --git a/commit.go b/commit.go index 124abe10cea..4df3b990883 100644 --- a/commit.go +++ b/commit.go @@ -12,14 +12,14 @@ import ( "github.com/containers/buildah/pkg/blobcache" "github.com/containers/buildah/util" - cp "github.com/containers/image/v4/copy" - "github.com/containers/image/v4/docker" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/signature" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/signature" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/stringid" diff --git a/common.go b/common.go index a8b29231d31..d2e9dc732aa 100644 --- a/common.go +++ b/common.go @@ -6,8 +6,8 @@ import ( "path/filepath" "github.com/containers/buildah/pkg/unshare" - cp "github.com/containers/image/v4/copy" - "github.com/containers/image/v4/types" + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/types" "github.com/containers/storage" ) diff --git a/config.go b/config.go index 49b1930c568..617619e45c1 100644 --- a/config.go +++ b/config.go @@ -8,9 +8,9 @@ import ( "time" "github.com/containers/buildah/docker" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/containers/storage/pkg/stringid" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" diff --git a/docker/types.go b/docker/types.go index 4b62e0e319e..561287ac276 100644 --- a/docker/types.go +++ b/docker/types.go @@ -7,7 +7,7 @@ package docker import ( "time" - "github.com/containers/image/v4/pkg/strslice" + "github.com/containers/image/v5/pkg/strslice" digest "github.com/opencontainers/go-digest" ) 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/docs/tutorials/04-include-in-your-build-tool.md b/docs/tutorials/04-include-in-your-build-tool.md index 74bee18c471..185f997d5a3 100644 --- a/docs/tutorials/04-include-in-your-build-tool.md +++ b/docs/tutorials/04-include-in-your-build-tool.md @@ -102,8 +102,8 @@ import ( "fmt" "github.com/containers/buildah" "github.com/containers/buildah/pkg/unshare" - "github.com/containers/image/v4/transports/alltransports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" "github.com/containers/storage" ) diff --git a/go.mod b/go.mod index 931ca1d0b2f..6bba4a1f8eb 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.12 require ( github.com/blang/semver v3.5.0+incompatible // indirect github.com/containernetworking/cni v0.7.1 - github.com/containers/image/v4 v4.0.1 + github.com/containers/image/v5 v5.0.0 github.com/containers/storage v1.13.5 github.com/cyphar/filepath-securejoin v0.2.2 github.com/docker/distribution v2.7.1+incompatible diff --git a/go.sum b/go.sum index f45329b61f2..e5ce6a290ed 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,8 @@ github.com/containernetworking/cni v0.7.1 h1:fE3r16wpSEyaqY4Z4oFrLMmIGfBYIKpPrHK github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= github.com/containers/image/v4 v4.0.1 h1:idNGHChj0Pyv3vLrxul2oSVMZLeFqpoq3CjLeVgapSQ= github.com/containers/image/v4 v4.0.1/go.mod h1:0ASJH1YgJiX/eqFZObqepgsvIA4XjCgpyfwn9pDGafA= +github.com/containers/image/v5 v5.0.0 h1:arnXgbt1ucsC/ndtSpiQY87rA0UjhF+/xQnPzqdBDn4= +github.com/containers/image/v5 v5.0.0/go.mod h1:MgiLzCfIeo8lrHi+4Lb8HP+rh513sm0Mlk6RrhjFOLY= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b h1:Q8ePgVfHDplZ7U33NwHZkrVELsZP5fYj9pM5WBZB2GE= github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b/go.mod h1:9rfv8iPl1ZP7aqh9YA68wnZv2NUDbXdcdPHVz0pFbPY= github.com/containers/storage v1.13.4 h1:j0bBaJDKbUHtAW1MXPFnwXJtqcH+foWeuXK1YaBV5GA= diff --git a/image.go b/image.go index 829d8c21d57..79c75ce0bd3 100644 --- a/image.go +++ b/image.go @@ -13,11 +13,11 @@ import ( "time" "github.com/containers/buildah/docker" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/image" - "github.com/containers/image/v4/manifest" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/manifest" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/ioutils" @@ -596,7 +596,7 @@ func (i *containerImageSource) GetManifest(ctx context.Context, instanceDigest * return i.manifest, i.manifestType, nil } -func (i *containerImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { +func (i *containerImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) { return nil, nil } diff --git a/imagebuildah/build.go b/imagebuildah/build.go index fe6cc266a8f..6b2c9c84c2c 100644 --- a/imagebuildah/build.go +++ b/imagebuildah/build.go @@ -13,8 +13,8 @@ import ( "strings" "github.com/containers/buildah" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/opencontainers/runc/libcontainer/configs" diff --git a/imagebuildah/executor.go b/imagebuildah/executor.go index 136261bf0e2..27ec1bb23bb 100644 --- a/imagebuildah/executor.go +++ b/imagebuildah/executor.go @@ -12,11 +12,11 @@ import ( "github.com/containers/buildah" "github.com/containers/buildah/util" - "github.com/containers/image/v4/docker/reference" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/transports/alltransports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + is "github.com/containers/image/v5/storage" + "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/containers/storage/pkg/archive" v1 "github.com/opencontainers/image-spec/specs-go/v1" diff --git a/imagebuildah/stage_executor.go b/imagebuildah/stage_executor.go index 3bdef98d1d9..fad2bfe9558 100644 --- a/imagebuildah/stage_executor.go +++ b/imagebuildah/stage_executor.go @@ -13,12 +13,12 @@ import ( "github.com/containers/buildah" buildahdocker "github.com/containers/buildah/docker" "github.com/containers/buildah/util" - cp "github.com/containers/image/v4/copy" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" securejoin "github.com/cyphar/filepath-securejoin" diff --git a/import.go b/import.go index 4d30595276d..751ce6ae1c8 100644 --- a/import.go +++ b/import.go @@ -5,9 +5,9 @@ import ( "github.com/containers/buildah/docker" "github.com/containers/buildah/util" - "github.com/containers/image/v4/manifest" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/manifest" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/types" "github.com/containers/storage" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" diff --git a/manifests/copy.go b/manifests/copy.go new file mode 100644 index 00000000000..7e651a46c02 --- /dev/null +++ b/manifests/copy.go @@ -0,0 +1,15 @@ +package manifests + +import ( + "github.com/containers/image/v5/signature" +) + +var ( + // storageAllowedPolicyScopes overrides the policy for local storage + // to ensure that we can read images from it. + storageAllowedPolicyScopes = signature.PolicyTransportScopes{ + "": []signature.PolicyRequirement{ + signature.NewPRInsecureAcceptAnything(), + }, + } +) diff --git a/manifests/manifests.go b/manifests/manifests.go new file mode 100644 index 00000000000..cfd141830db --- /dev/null +++ b/manifests/manifests.go @@ -0,0 +1,383 @@ +package manifests + +import ( + "context" + "encoding/json" + stderrors "errors" + "io" + + "github.com/containers/buildah/pkg/manifests" + "github.com/containers/buildah/pkg/supplemented" + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/signature" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + "github.com/containers/storage" + digest "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const instancesData = "instances.json" + +// ErrListImageUnknown is returned when we attempt to create an image reference +// for a List that has not yet been saved to an image. +var ErrListImageUnknown = stderrors.New("unable to determine which image holds the manifest list") + +type list struct { + manifests.List + instances map[digest.Digest]string +} + +// List is a manifest list or image index, either created using Create(), or +// loaded from local storage using LoadFromImage(). +type List interface { + manifests.List + SaveToImage(store storage.Store, imageID string, names []string, mimeType string) (string, error) + Reference(store storage.Store, multiple cp.ImageListSelection, instances []digest.Digest) (types.ImageReference, error) + Push(ctx context.Context, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error) + Add(ctx context.Context, sys *types.SystemContext, ref types.ImageReference, all bool) (digest.Digest, error) +} + +// PushOptions includes various settings which are needed for pushing the +// manifest list and its instances. +type PushOptions struct { + Store storage.Store + SystemContext *types.SystemContext // github.com/containers/image/types.SystemContext + ImageListSelection cp.ImageListSelection // set to either CopySystemImage, CopyAllImages, or CopySpecificImages + Instances []digest.Digest // instances to copy if ImageListSelection == CopySpecificImages + ReportWriter io.Writer // will be used to log the writing of the list and any blobs +} + +// Create creates a new list containing information about the specified image, +// computing its manifest's digest, and retrieving OS and architecture +// information from its configuration blob. Returns the new list, and the +// instanceDigest for the initial image. +func Create() List { + return &list{ + List: manifests.Create(), + instances: make(map[digest.Digest]string), + } +} + +// LoadFromImage reads the manifest list or image index, and additional +// information about where the various instances that it contains live, from an +// image record with the specified ID in local storage. +func LoadFromImage(store storage.Store, image string) (string, List, error) { + img, err := store.Image(image) + if err != nil { + return "", nil, errors.Wrapf(err, "error locating image %q for loading manifest list", image) + } + manifestBytes, err := store.ImageBigData(img.ID, storage.ImageDigestManifestBigDataNamePrefix) + if err != nil { + return "", nil, errors.Wrapf(err, "error locating image %q for loading manifest list", image) + } + manifestList, err := manifests.FromBlob(manifestBytes) + if err != nil { + return "", nil, err + } + list := &list{ + List: manifestList, + instances: make(map[digest.Digest]string), + } + instancesBytes, err := store.ImageBigData(img.ID, instancesData) + if err != nil { + return "", nil, errors.Wrapf(err, "error locating image %q for loading instance list", image) + } + if err := json.Unmarshal(instancesBytes, &list.instances); err != nil { + return "", nil, errors.Wrapf(err, "error decoding instance list for image %q", image) + } + list.instances[""] = img.ID + return img.ID, list, err +} + +// SaveToImage saves the manifest list or image index as the manifest of an +// Image record with the specified names in local storage, generating a random +// image ID if none is specified. It also stores information about where the +// images whose manifests are included in the list can be found. +func (l *list) SaveToImage(store storage.Store, imageID string, names []string, mimeType string) (string, error) { + manifestBytes, err := l.List.Serialize(mimeType) + if err != nil { + return "", err + } + instancesBytes, err := json.Marshal(&l.instances) + if err != nil { + return "", err + } + img, err := store.CreateImage(imageID, names, "", "", &storage.ImageOptions{}) + if err == nil || errors.Cause(err) == storage.ErrDuplicateID { + created := (err == nil) + if created { + imageID = img.ID + l.instances[""] = img.ID + } + err := store.SetImageBigData(imageID, storage.ImageDigestManifestBigDataNamePrefix, manifestBytes, manifest.Digest) + if err != nil { + if created { + if _, err2 := store.DeleteImage(img.ID, true); err2 != nil { + logrus.Errorf("error deleting image %q after failing to save manifest for it", img.ID) + } + } + return "", errors.Wrapf(err, "error saving manifest list to image %q", imageID) + } + err = store.SetImageBigData(imageID, instancesData, instancesBytes, nil) + if err != nil { + if created { + if _, err2 := store.DeleteImage(img.ID, true); err2 != nil { + logrus.Errorf("error deleting image %q after failing to save instance locations for it", img.ID) + } + } + return "", errors.Wrapf(err, "error saving instance list to image %q", imageID) + } + return imageID, nil + } + return "", errors.Wrapf(err, "error creating image to hold manifest list") +} + +// Reference returns an image reference for the composite image being built +// in the list, or an error if the list has never been saved to a local image. +func (l *list) Reference(store storage.Store, multiple cp.ImageListSelection, instances []digest.Digest) (types.ImageReference, error) { + if l.instances[""] == "" { + return nil, errors.Wrap(ErrListImageUnknown, "error building reference to list") + } + s, err := is.Transport.ParseStoreReference(store, l.instances[""]) + if err != nil { + return nil, errors.Wrapf(err, "error creating ImageReference from image %q", l.instances[""]) + } + references := make([]types.ImageReference, 0, len(l.instances)) + whichInstances := make([]digest.Digest, 0, len(l.instances)) + switch multiple { + case cp.CopyAllImages, cp.CopySystemImage: + for instance := range l.instances { + if instance != "" { + whichInstances = append(whichInstances, instance) + } + } + case cp.CopySpecificImages: + for instance := range l.instances { + for _, allowed := range instances { + if instance == allowed { + whichInstances = append(whichInstances, instance) + } + } + } + } + for _, instance := range whichInstances { + imageName := l.instances[instance] + ref, err := alltransports.ParseImageName(imageName) + if err != nil { + return nil, errors.Wrapf(err, "error creating ImageReference from image %q", imageName) + } + references = append(references, ref) + } + return supplemented.Reference(s, references, multiple, instances), nil +} + +// Push saves the manifest list and whichever blobs are needed to a destination location. +func (l *list) Push(ctx context.Context, dest types.ImageReference, options PushOptions) (reference.Canonical, digest.Digest, error) { + // Load the system signing policy. + pushPolicy, err := signature.DefaultPolicy(options.SystemContext) + if err != nil { + return nil, "", errors.Wrapf(err, "error obtaining default signature policy") + } + + // Override the settings for local storage to make sure that we can always read the source "image". + pushPolicy.Transports[is.Transport.Name()] = storageAllowedPolicyScopes + + policyContext, err := signature.NewPolicyContext(pushPolicy) + if err != nil { + return nil, "", errors.Wrapf(err, "error creating new signature policy context") + } + defer func() { + if err2 := policyContext.Destroy(); err2 != nil { + logrus.Errorf("error destroying signature policy context: %v", err2) + } + }() + + // Build a source reference for our list and grab bag full of blobs. + src, err := l.Reference(options.Store, options.ImageListSelection, options.Instances) + if err != nil { + return nil, "", err + } + copyOptions := &cp.Options{ + ImageListSelection: options.ImageListSelection, + Instances: options.Instances, + SourceCtx: options.SystemContext, + DestinationCtx: options.SystemContext, + ReportWriter: options.ReportWriter, + } + + // Copy whatever we were asked to copy. + manifestBytes, err := cp.Image(ctx, policyContext, dest, src, copyOptions) + if err != nil { + return nil, "", err + } + manifestDigest, err := manifest.Digest(manifestBytes) + if err != nil { + return nil, "", err + } + return nil, manifestDigest, nil +} + +// Add adds information about the specified image to the list, computing the +// image's manifest's digest, retrieving OS and architecture information from +// the image's configuration, and recording the image's reference so that it +// can be found at push-time. Returns the instanceDigest for the image. If +// the reference points to an image list, either all instances are added (if +// "all" is true), or the instance which matches "sys" (if "all" is false) will +// be added. +func (l *list) Add(ctx context.Context, sys *types.SystemContext, ref types.ImageReference, all bool) (digest.Digest, error) { + src, err := ref.NewImageSource(ctx, sys) + if err != nil { + return "", errors.Wrapf(err, "error setting up to read manifest and configuration from %q", transports.ImageName(ref)) + } + defer src.Close() + + type instanceInfo struct { + instanceDigest *digest.Digest + OS, Architecture, OSVersion, Variant string + Features, OSFeatures, Annotations []string + Size int64 + } + var instanceInfos []instanceInfo + var manifestDigest digest.Digest + + manifestBytes, manifestType, err := src.GetManifest(ctx, nil) + if err != nil { + return "", errors.Wrapf(err, "error reading manifest from %q", transports.ImageName(ref)) + } + + if manifest.MIMETypeIsMultiImage(manifestType) { + list, err := manifest.ListFromBlob(manifestBytes, manifestType) + if err != nil { + return "", errors.Wrapf(err, "error parsing manifest list in %q", transports.ImageName(ref)) + } + if all { + lists, err := manifests.FromBlob(manifestBytes) + if err != nil { + return "", errors.Wrapf(err, "error parsing manifest list in %q", transports.ImageName(ref)) + } + for i, instance := range lists.OCIv1().Manifests { + platform := instance.Platform + if platform == nil { + platform = &v1.Platform{} + } + instanceDigest := instance.Digest + instanceInfo := instanceInfo{ + instanceDigest: &instanceDigest, + OS: platform.OS, + Architecture: platform.Architecture, + OSVersion: platform.OSVersion, + Variant: platform.Variant, + Features: append([]string{}, lists.Docker().Manifests[i].Platform.Features...), + OSFeatures: append([]string{}, platform.OSFeatures...), + Size: instance.Size, + } + instanceInfos = append(instanceInfos, instanceInfo) + } + } else { + instanceDigest, err := list.ChooseInstance(sys) + if err != nil { + return "", errors.Wrapf(err, "error selecting image from manifest list in %q", transports.ImageName(ref)) + } + lists, err := manifests.FromBlob(manifestBytes) + if err != nil { + return "", errors.Wrapf(err, "error parsing manifest list in %q", transports.ImageName(ref)) + } + added := false + for i, instance := range lists.OCIv1().Manifests { + if instance.Digest != instanceDigest { + continue + } + platform := instance.Platform + if platform == nil { + platform = &v1.Platform{} + } + instanceInfo := instanceInfo{ + instanceDigest: &instanceDigest, + OS: platform.OS, + Architecture: platform.Architecture, + OSVersion: platform.OSVersion, + Variant: platform.Variant, + Features: append([]string{}, lists.Docker().Manifests[i].Platform.Features...), + OSFeatures: append([]string{}, platform.OSFeatures...), + Size: instance.Size, + } + instanceInfos = append(instanceInfos, instanceInfo) + added = true + } + if !added { + instanceInfo := instanceInfo{ + instanceDigest: &instanceDigest, + } + instanceInfos = append(instanceInfos, instanceInfo) + } + } + } else { + instanceInfo := instanceInfo{ + instanceDigest: nil, + } + instanceInfos = append(instanceInfos, instanceInfo) + } + + for _, instanceInfo := range instanceInfos { + if instanceInfo.OS == "" || instanceInfo.Architecture == "" { + img, err := image.FromUnparsedImage(ctx, sys, image.UnparsedInstance(src, instanceInfo.instanceDigest)) + if err != nil { + return "", errors.Wrapf(err, "error reading configuration blob from %q", transports.ImageName(ref)) + } + config, err := img.OCIConfig(ctx) + if err != nil { + return "", errors.Wrapf(err, "error reading info about config blob from %q", transports.ImageName(ref)) + } + if instanceInfo.OS == "" { + instanceInfo.OS = config.OS + } + if instanceInfo.Architecture == "" { + instanceInfo.Architecture = config.Architecture + } + } + if instanceInfo.instanceDigest == nil { + manifestBytes, manifestType, err = src.GetManifest(ctx, instanceInfo.instanceDigest) + if err != nil { + return "", errors.Wrapf(err, "error reading manifest from %q, instance %q", transports.ImageName(ref), instanceInfo.instanceDigest) + } + manifestDigest, err = manifest.Digest(manifestBytes) + if err != nil { + return "", errors.Wrapf(err, "error computing digest of manifest from %q", transports.ImageName(ref)) + } + instanceInfo.instanceDigest = &manifestDigest + instanceInfo.Size = int64(len(manifestBytes)) + } else { + if manifestDigest == "" { + manifestDigest = *instanceInfo.instanceDigest + } + } + err = l.List.AddInstance(*instanceInfo.instanceDigest, instanceInfo.Size, manifestType, instanceInfo.OS, instanceInfo.Architecture, instanceInfo.OSVersion, instanceInfo.OSFeatures, instanceInfo.Variant, instanceInfo.Features, instanceInfo.Annotations) + if err != nil { + return "", errors.Wrapf(err, "error adding instance with digest %q", *instanceInfo.instanceDigest) + } + if _, ok := l.instances[*instanceInfo.instanceDigest]; !ok { + l.instances[*instanceInfo.instanceDigest] = transports.ImageName(ref) + } + } + + return manifestDigest, nil +} + +// Remove filters out any instances in the list which match the specified digest. +func (l *list) Remove(instanceDigest digest.Digest) error { + err := l.List.Remove(instanceDigest) + if err == nil { + if _, needToDelete := l.instances[instanceDigest]; needToDelete { + delete(l.instances, instanceDigest) + } + } + return err +} diff --git a/manifests/manifests_test.go b/manifests/manifests_test.go new file mode 100644 index 00000000000..5ae070c6246 --- /dev/null +++ b/manifests/manifests_test.go @@ -0,0 +1,306 @@ +package manifests + +import ( + "context" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/containers/buildah/pkg/manifests" + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + "github.com/containers/storage" + digest "github.com/opencontainers/go-digest" + "github.com/stretchr/testify/assert" +) + +var ( + _ List = &list{} + sys = &types.SystemContext{} + amd64sys = &types.SystemContext{ArchitectureChoice: "amd64"} + arm64sys = &types.SystemContext{ArchitectureChoice: "arm64"} + ppc64sys = &types.SystemContext{ArchitectureChoice: "ppc64le"} +) + +const ( + listImageName = "foo" + + otherListImage = "docker://k8s.gcr.io/pause:3.1" + otherListDigest = "sha256:f78411e19d84a252e53bff71a4407a5686c46983a2c2eeed83929b888179acea" + otherListAmd64Digest = "sha256:59eec8837a4d942cc19a52b8c09ea75121acc38114a2c68b98983ce9356b8610" + otherListArm64Digest = "sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39" + otherListPpc64Digest = "sha256:bcf9771c0b505e68c65440474179592ffdfa98790eb54ffbf129969c5e429990" + otherListInstanceDigest = "docker://k8s.gcr.io/pause@sha256:f365626a556e58189fc21d099fc64603db0f440bff07f77c740989515c544a39" +) + +func TestSaveLoad(t *testing.T) { + dir, err := ioutil.TempDir("", "manifests") + assert.Nilf(t, err, "error creating temporary directory") + defer os.RemoveAll(dir) + + storeOptions := storage.StoreOptions{ + GraphRoot: filepath.Join(dir, "root"), + RunRoot: filepath.Join(dir, "runroot"), + GraphDriverName: "vfs", + } + store, err := storage.GetStore(storeOptions) + assert.Nilf(t, err, "error opening store") + defer func() { + if _, err := store.Shutdown(true); err != nil { + assert.Nilf(t, err, "error closing store") + } + }() + + list := Create() + assert.NotNil(t, list, "Create() returned nil?") + + image, err := list.SaveToImage(store, "", []string{listImageName}, manifest.DockerV2ListMediaType) + assert.Nilf(t, err, "SaveToImage(1)") + imageReused, err := list.SaveToImage(store, image, nil, manifest.DockerV2ListMediaType) + assert.Nilf(t, err, "SaveToImage(2)") + + _, list, err = LoadFromImage(store, image) + assert.Nilf(t, err, "LoadFromImage(1)") + assert.NotNilf(t, list, "LoadFromImage(1)") + _, list, err = LoadFromImage(store, imageReused) + assert.Nilf(t, err, "LoadFromImage(2)") + assert.NotNilf(t, list, "LoadFromImage(2)") + _, list, err = LoadFromImage(store, listImageName) + assert.Nilf(t, err, "LoadFromImage(3)") + assert.NotNilf(t, list, "LoadFromImage(3)") +} + +func TestAddRemove(t *testing.T) { + ctx := context.TODO() + + ref, err := alltransports.ParseImageName(otherListImage) + assert.Nilf(t, err, "ParseImageName(%q)", otherListImage) + src, err := ref.NewImageSource(ctx, sys) + assert.Nilf(t, err, "NewImageSource(%q)", otherListImage) + defer assert.Nilf(t, src.Close(), "ImageSource.Close()") + m, _, err := src.GetManifest(ctx, nil) + assert.Nilf(t, err, "ImageSource.GetManifest()") + assert.Nilf(t, src.Close(), "ImageSource.GetManifest()") + listDigest, err := manifest.Digest(m) + assert.Nilf(t, err, "manifest.Digest()") + assert.Equalf(t, listDigest.String(), otherListDigest, "digest for image %q changed?", otherListImage) + + l, err := manifests.FromBlob(m) + assert.Nilf(t, err, "manifests.FromBlob()") + assert.NotNilf(t, l, "manifests.FromBlob()") + assert.Equalf(t, len(l.Instances()), 5, "image %q had an arch added?", otherListImage) + + list := Create() + instanceDigest, err := list.Add(ctx, amd64sys, ref, false) + assert.Nilf(t, err, "list.Add(all=false)") + assert.Equal(t, instanceDigest.String(), otherListAmd64Digest) + assert.Equalf(t, len(list.Instances()), 1, "too many instances added") + + list = Create() + instanceDigest, err = list.Add(ctx, arm64sys, ref, false) + assert.Nilf(t, err, "list.Add(all=false)") + assert.Equal(t, instanceDigest.String(), otherListArm64Digest) + assert.Equalf(t, len(list.Instances()), 1, "too many instances added") + + list = Create() + instanceDigest, err = list.Add(ctx, ppc64sys, ref, false) + assert.Nilf(t, err, "list.Add(all=false)") + assert.Equal(t, instanceDigest.String(), otherListPpc64Digest) + assert.Equalf(t, len(list.Instances()), 1, "too many instances added") + + _, err = list.Add(ctx, sys, ref, true) + assert.Nilf(t, err, "list.Add(all=true)") + assert.Equalf(t, len(list.Instances()), 5, "too many instances added") + + list = Create() + _, err = list.Add(ctx, sys, ref, true) + assert.Nilf(t, err, "list.Add(all=true)") + assert.Equalf(t, len(list.Instances()), 5, "too many instances added", otherListImage) + + for _, instance := range list.Instances() { + assert.Nilf(t, list.Remove(instance), "error removing instance %q", instance) + } + assert.Equalf(t, len(list.Instances()), 0, "should have removed all instances") + + ref, err = alltransports.ParseImageName(otherListInstanceDigest) + assert.Nilf(t, err, "ParseImageName(%q)", otherListInstanceDigest) + + list = Create() + _, err = list.Add(ctx, sys, ref, false) + assert.Nilf(t, err, "list.Add(all=false)") + assert.Equalf(t, len(list.Instances()), 1, "too many instances added", otherListInstanceDigest) + + list = Create() + _, err = list.Add(ctx, sys, ref, true) + assert.Nilf(t, err, "list.Add(all=true)") + assert.Equalf(t, len(list.Instances()), 1, "too many instances added", otherListInstanceDigest) +} + +func TestReference(t *testing.T) { + ctx := context.TODO() + + dir, err := ioutil.TempDir("", "manifests") + assert.Nilf(t, err, "error creating temporary directory") + defer os.RemoveAll(dir) + + storeOptions := storage.StoreOptions{ + GraphRoot: filepath.Join(dir, "root"), + RunRoot: filepath.Join(dir, "runroot"), + GraphDriverName: "vfs", + } + store, err := storage.GetStore(storeOptions) + assert.Nilf(t, err, "error opening store") + defer func() { + if _, err := store.Shutdown(true); err != nil { + assert.Nilf(t, err, "error closing store") + } + }() + + ref, err := alltransports.ParseImageName(otherListImage) + assert.Nilf(t, err, "ParseImageName(%q)", otherListImage) + + list := Create() + _, err = list.Add(ctx, ppc64sys, ref, false) + assert.Nilf(t, err, "list.Add(all=false)") + + listRef, err := list.Reference(store, cp.CopyAllImages, nil) + assert.NotNilf(t, err, "list.Reference(never saved)") + assert.Nilf(t, listRef, "list.Reference(never saved)") + + listRef, err = list.Reference(store, cp.CopyAllImages, nil) + assert.NotNilf(t, err, "list.Reference(never saved)") + assert.Nilf(t, listRef, "list.Reference(never saved)") + + listRef, err = list.Reference(store, cp.CopySystemImage, nil) + assert.NotNilf(t, err, "list.Reference(never saved)") + assert.Nilf(t, listRef, "list.Reference(never saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest}) + assert.NotNilf(t, err, "list.Reference(never saved)") + assert.Nilf(t, listRef, "list.Reference(never saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest}) + assert.NotNilf(t, err, "list.Reference(never saved)") + assert.Nilf(t, listRef, "list.Reference(never saved)") + + _, err = list.SaveToImage(store, "", []string{listImageName}, "") + assert.Nilf(t, err, "SaveToImage") + + listRef, err = list.Reference(store, cp.CopyAllImages, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySystemImage, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest}) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest}) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + _, err = list.Add(ctx, sys, ref, true) + assert.Nilf(t, err, "list.Add(all=true)") + + listRef, err = list.Reference(store, cp.CopyAllImages, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySystemImage, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, nil) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest}) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") + + listRef, err = list.Reference(store, cp.CopySpecificImages, []digest.Digest{otherListAmd64Digest, otherListArm64Digest}) + assert.Nilf(t, err, "list.Reference(saved)") + assert.NotNilf(t, listRef, "list.Reference(saved)") +} + +func TestPush(t *testing.T) { + ctx := context.TODO() + + dir, err := ioutil.TempDir("", "manifests") + assert.Nilf(t, err, "error creating temporary directory") + defer os.RemoveAll(dir) + + storeOptions := storage.StoreOptions{ + GraphRoot: filepath.Join(dir, "root"), + RunRoot: filepath.Join(dir, "runroot"), + GraphDriverName: "vfs", + } + store, err := storage.GetStore(storeOptions) + assert.Nilf(t, err, "error opening store") + defer func() { + if _, err := store.Shutdown(true); err != nil { + assert.Nilf(t, err, "error closing store") + } + }() + + dest, err := ioutil.TempDir("", "manifests") + assert.Nilf(t, err, "error creating temporary directory") + defer os.RemoveAll(dest) + + destRef, err := alltransports.ParseImageName(fmt.Sprintf("dir:%s", dest)) + assert.Nilf(t, err, "ParseImageName()") + + ref, err := alltransports.ParseImageName(otherListImage) + assert.Nilf(t, err, "ParseImageName(%q)", otherListImage) + + list := Create() + _, err = list.Add(ctx, sys, ref, true) + assert.Nilf(t, err, "list.Add(all=true)") + + _, err = list.SaveToImage(store, "", []string{listImageName}, "") + assert.Nilf(t, err, "SaveToImage") + + options := PushOptions{ + Store: store, + SystemContext: sys, + ImageListSelection: cp.CopyAllImages, + Instances: nil, + } + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(all)") + + options.ImageListSelection = cp.CopySystemImage + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(local)") + + options.ImageListSelection = cp.CopySpecificImages + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(none specified)") + + options.Instances = []digest.Digest{otherListAmd64Digest} + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(one specified)") + + options.Instances = append(options.Instances, otherListArm64Digest) + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(two specified)") + + options.Instances = append(options.Instances, otherListPpc64Digest) + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(three specified)") + + options.Instances = append(options.Instances, otherListDigest) + _, _, err = list.Push(ctx, destRef, options) + assert.Nilf(t, err, "list.Push(four specified)") +} diff --git a/new.go b/new.go index 216a96611ad..87cfd5d0da6 100644 --- a/new.go +++ b/new.go @@ -7,12 +7,12 @@ import ( "strings" "github.com/containers/buildah/util" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/pkg/sysregistriesv2" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/transports/alltransports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/sysregistriesv2" + is "github.com/containers/image/v5/storage" + "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/openshift/imagebuilder" "github.com/pkg/errors" diff --git a/pkg/blobcache/blobcache.go b/pkg/blobcache/blobcache.go index 539c894a360..b7f70461535 100644 --- a/pkg/blobcache/blobcache.go +++ b/pkg/blobcache/blobcache.go @@ -10,11 +10,11 @@ import ( "sync" "github.com/containers/buildah/docker" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/image" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/ioutils" digest "github.com/opencontainers/go-digest" @@ -263,14 +263,14 @@ func (s *blobCacheSource) GetSignatures(ctx context.Context, instanceDigest *dig return s.source.GetSignatures(ctx, instanceDigest) } -func (s *blobCacheSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { - signatures, err := s.source.GetSignatures(ctx, nil) +func (s *blobCacheSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) { + signatures, err := s.source.GetSignatures(ctx, instanceDigest) if err != nil { return nil, errors.Wrapf(err, "error checking if image %q has signatures", transports.ImageName(s.reference)) } canReplaceBlobs := !(len(signatures) > 0 && len(signatures[0]) > 0) - infos, err := s.source.LayerInfosForCopy(ctx) + infos, err := s.source.LayerInfosForCopy(ctx, instanceDigest) if err != nil { return nil, errors.Wrapf(err, "error getting layer infos for copying image %q through cache", transports.ImageName(s.reference)) } @@ -515,7 +515,7 @@ func (d *blobCacheDestination) TryReusingBlob(ctx context.Context, info types.Bl return false, types.BlobInfo{}, nil } -func (d *blobCacheDestination) PutManifest(ctx context.Context, manifestBytes []byte) error { +func (d *blobCacheDestination) PutManifest(ctx context.Context, manifestBytes []byte, instanceDigest *digest.Digest) error { manifestDigest, err := manifest.Digest(manifestBytes) if err != nil { logrus.Warnf("error digesting manifest %q: %v", string(manifestBytes), err) @@ -525,13 +525,13 @@ func (d *blobCacheDestination) PutManifest(ctx context.Context, manifestBytes [] logrus.Warnf("error saving manifest as %q: %v", filename, err) } } - return d.destination.PutManifest(ctx, manifestBytes) + return d.destination.PutManifest(ctx, manifestBytes, instanceDigest) } -func (d *blobCacheDestination) PutSignatures(ctx context.Context, signatures [][]byte) error { - return d.destination.PutSignatures(ctx, signatures) +func (d *blobCacheDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error { + return d.destination.PutSignatures(ctx, signatures, instanceDigest) } -func (d *blobCacheDestination) Commit(ctx context.Context) error { - return d.destination.Commit(ctx) +func (d *blobCacheDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error { + return d.destination.Commit(ctx, unparsedToplevel) } diff --git a/pkg/blobcache/blobcache_test.go b/pkg/blobcache/blobcache_test.go index bf7b8be87da..0475d09e4fe 100644 --- a/pkg/blobcache/blobcache_test.go +++ b/pkg/blobcache/blobcache_test.go @@ -13,11 +13,12 @@ import ( "strings" "testing" - cp "github.com/containers/image/v4/copy" - "github.com/containers/image/v4/pkg/blobinfocache/none" - "github.com/containers/image/v4/signature" - "github.com/containers/image/v4/transports/alltransports" - "github.com/containers/image/v4/types" + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/pkg/blobinfocache/none" + "github.com/containers/image/v5/signature" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" "github.com/containers/storage/pkg/archive" digest "github.com/opencontainers/go-digest" specs "github.com/opencontainers/image-spec/specs-go" @@ -67,6 +68,8 @@ func TestBlobCache(t *testing.T) { } }() + systemContext := types.SystemContext{} + for _, repeat := range []int{1, 10, 100, 1000, 10000} { for _, desiredCompression := range []types.LayerCompression{types.PreserveOriginal, types.Compress, types.Decompress} { for _, layerCompression := range []archive.Compression{archive.Uncompressed, archive.Gzip} { @@ -100,12 +103,14 @@ func TestBlobCache(t *testing.T) { SchemaVersion: 2, }, Config: v1.Descriptor{ - Digest: configInfo.Digest, - Size: configInfo.Size, + MediaType: v1.MediaTypeImageConfig, + Digest: configInfo.Digest, + Size: configInfo.Size, }, Layers: []v1.Descriptor{{ - Digest: blobInfo.Digest, - Size: blobInfo.Size, + MediaType: v1.MediaTypeImageLayer, + Digest: blobInfo.Digest, + Size: blobInfo.Size, }}, } manifestBytes, err := json.Marshal(&manifest) @@ -139,11 +144,21 @@ func TestBlobCache(t *testing.T) { if err != nil { t.Fatalf("error writing config blob to source image: %v", err) } - err = destImage.PutManifest(context.TODO(), manifestBytes) + srcImage, err := srcRef.NewImageSource(context.TODO(), &systemContext) + if err != nil { + t.Fatalf("error opening source image: %v", err) + } + defer func() { + err := srcImage.Close() + if err != nil { + t.Fatalf("error closing source image: %v", err) + } + }() + err = destImage.PutManifest(context.TODO(), manifestBytes, nil) if err != nil { t.Fatalf("error writing manifest to source image: %v", err) } - err = destImage.Commit(context.TODO()) + err = destImage.Commit(context.TODO(), image.UnparsedInstance(srcImage, nil)) if err != nil { t.Fatalf("error committing source image: %v", err) } @@ -210,7 +225,6 @@ func TestBlobCache(t *testing.T) { if err != nil { t.Fatalf("error parsing destination image name %q: %v", destName, err) } - systemContext := types.SystemContext{} options := cp.Options{ SourceCtx: &systemContext, DestinationCtx: &systemContext, 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/manifests/errors.go b/pkg/manifests/errors.go new file mode 100644 index 00000000000..8398d7efcfe --- /dev/null +++ b/pkg/manifests/errors.go @@ -0,0 +1,16 @@ +package manifests + +import ( + "errors" +) + +var ( + // ErrDigestNotFound is returned when we look for an image instance + // with a particular digest in a list or index, and fail to find it. + ErrDigestNotFound = errors.New("no image instance matching the specified digest was found in the list or index") + // ErrManifestTypeNotSupported is returned when we attempt to parse a + // manifest with a known MIME type as a list or index, or when we attempt + // to serialize a list or index to a manifest with a MIME type that we + // don't know how to encode. + ErrManifestTypeNotSupported = errors.New("manifest type not supported") +) diff --git a/pkg/manifests/manifests.go b/pkg/manifests/manifests.go new file mode 100644 index 00000000000..ea9495ee73a --- /dev/null +++ b/pkg/manifests/manifests.go @@ -0,0 +1,493 @@ +package manifests + +import ( + "encoding/json" + "os" + + "github.com/containers/image/v5/manifest" + digest "github.com/opencontainers/go-digest" + imgspec "github.com/opencontainers/image-spec/specs-go" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +// List is a generic interface for manipulating a manifest list or an image +// index. +type List interface { + AddInstance(manifestDigest digest.Digest, manifestSize int64, manifestType, os, architecture, osVersion string, osFeatures []string, variant string, features []string, annotations []string) error + Remove(instanceDigest digest.Digest) error + + SetURLs(instanceDigest digest.Digest, urls []string) error + URLs(instanceDigest digest.Digest) ([]string, error) + + SetAnnotations(instanceDigest *digest.Digest, annotations map[string]string) error + Annotations(instanceDigest *digest.Digest) (map[string]string, error) + + SetOS(instanceDigest digest.Digest, os string) error + OS(instanceDigest digest.Digest) (string, error) + + SetArchitecture(instanceDigest digest.Digest, arch string) error + Architecture(instanceDigest digest.Digest) (string, error) + + SetOSVersion(instanceDigest digest.Digest, osVersion string) error + OSVersion(instanceDigest digest.Digest) (string, error) + + SetVariant(instanceDigest digest.Digest, variant string) error + Variant(instanceDigest digest.Digest) (string, error) + + SetFeatures(instanceDigest digest.Digest, features []string) error + Features(instanceDigest digest.Digest) ([]string, error) + + SetOSFeatures(instanceDigest digest.Digest, osFeatures []string) error + OSFeatures(instanceDigest digest.Digest) ([]string, error) + + Serialize(mimeType string) ([]byte, error) + Instances() []digest.Digest + OCIv1() *v1.Index + Docker() *manifest.Schema2List + + findDocker(instanceDigest digest.Digest) (*manifest.Schema2ManifestDescriptor, error) + findOCIv1(instanceDigest digest.Digest) (*v1.Descriptor, error) +} + +type list struct { + docker manifest.Schema2List + oci v1.Index +} + +// OCIv1 returns the list as a Docker schema 2 list. The returned structure should NOT be modified. +func (l *list) Docker() *manifest.Schema2List { + return &l.docker +} + +// OCIv1 returns the list as an OCI image index. The returned structure should NOT be modified. +func (l *list) OCIv1() *v1.Index { + return &l.oci +} + +// Create creates a new list. +func Create() List { + return &list{ + docker: manifest.Schema2List{ + SchemaVersion: 2, + MediaType: manifest.DockerV2ListMediaType, + }, + oci: v1.Index{ + Versioned: imgspec.Versioned{SchemaVersion: 2}, + }, + } +} + +// AddInstance adds an entry for the specified manifest digest, with assorted +// additional information specified in parameters, to the list or index. +func (l *list) AddInstance(manifestDigest digest.Digest, manifestSize int64, manifestType, osName, architecture, osVersion string, osFeatures []string, variant string, features []string, annotations []string) error { + if err := l.Remove(manifestDigest); err != nil && !os.IsNotExist(errors.Cause(err)) { + return err + } + + schema2platform := manifest.Schema2PlatformSpec{ + Architecture: architecture, + OS: osName, + OSVersion: osVersion, + OSFeatures: osFeatures, + Variant: variant, + Features: features, + } + l.docker.Manifests = append(l.docker.Manifests, manifest.Schema2ManifestDescriptor{ + Schema2Descriptor: manifest.Schema2Descriptor{ + MediaType: manifestType, + Size: manifestSize, + Digest: manifestDigest, + }, + Platform: schema2platform, + }) + + ociv1platform := v1.Platform{ + Architecture: architecture, + OS: osName, + OSVersion: osVersion, + OSFeatures: osFeatures, + Variant: variant, + } + l.oci.Manifests = append(l.oci.Manifests, v1.Descriptor{ + MediaType: manifestType, + Size: manifestSize, + Digest: manifestDigest, + Platform: &ociv1platform, + }) + + return nil +} + +// Remove filters out any instances in the list which match the specified digest. +func (l *list) Remove(instanceDigest digest.Digest) error { + err := errors.Wrapf(os.ErrNotExist, "no instance matching digest %q found in manifest list", instanceDigest) + newDockerManifests := make([]manifest.Schema2ManifestDescriptor, 0, len(l.docker.Manifests)) + for i := range l.docker.Manifests { + if l.docker.Manifests[i].Digest != instanceDigest { + newDockerManifests = append(newDockerManifests, l.docker.Manifests[i]) + } else { + err = nil + } + } + l.docker.Manifests = newDockerManifests + newOCIv1Manifests := make([]v1.Descriptor, 0, len(l.oci.Manifests)) + for i := range l.oci.Manifests { + if l.oci.Manifests[i].Digest != instanceDigest { + newOCIv1Manifests = append(newOCIv1Manifests, l.oci.Manifests[i]) + } else { + err = nil + } + } + l.oci.Manifests = newOCIv1Manifests + return err +} + +func (l *list) findDocker(instanceDigest digest.Digest) (*manifest.Schema2ManifestDescriptor, error) { + for i := range l.docker.Manifests { + if l.docker.Manifests[i].Digest == instanceDigest { + return &l.docker.Manifests[i], nil + } + } + return nil, errors.Wrapf(ErrDigestNotFound, "no Docker manifest matching digest %q was found in list", instanceDigest.String()) +} + +func (l *list) findOCIv1(instanceDigest digest.Digest) (*v1.Descriptor, error) { + for i := range l.oci.Manifests { + if l.oci.Manifests[i].Digest == instanceDigest { + return &l.oci.Manifests[i], nil + } + } + return nil, errors.Wrapf(ErrDigestNotFound, "no OCI manifest matching digest %q was found in list", instanceDigest.String()) +} + +// SetURLs sets the URLs where the manifest might also be found. +func (l *list) SetURLs(instanceDigest digest.Digest, urls []string) error { + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return err + } + docker, err := l.findDocker(instanceDigest) + if err != nil { + return err + } + oci.URLs = append([]string{}, urls...) + docker.URLs = append([]string{}, urls...) + return nil +} + +// URLs retrieves the locations from which this object might possibly be downloaded. +func (l *list) URLs(instanceDigest digest.Digest) ([]string, error) { + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return nil, err + } + return append([]string{}, oci.URLs...), nil +} + +// SetAnnotations sets annotations on the image index, or on a specific manifest. +// The field is specific to the OCI image index format, and is not present in Docker manifest lists. +func (l *list) SetAnnotations(instanceDigest *digest.Digest, annotations map[string]string) error { + a := &l.oci.Annotations + if instanceDigest != nil { + oci, err := l.findOCIv1(*instanceDigest) + if err != nil { + return err + } + a = &oci.Annotations + } + (*a) = make(map[string]string) + for k, v := range annotations { + (*a)[k] = v + } + return nil +} + +// Annotations retrieves the annotations which have been set on the image index, or on one instance. +// The field is specific to the OCI image index format, and is not present in Docker manifest lists. +func (l *list) Annotations(instanceDigest *digest.Digest) (map[string]string, error) { + a := l.oci.Annotations + if instanceDigest != nil { + oci, err := l.findOCIv1(*instanceDigest) + if err != nil { + return nil, err + } + a = oci.Annotations + } + annotations := make(map[string]string) + for k, v := range a { + annotations[k] = v + } + return annotations, nil +} + +// SetOS sets the OS field in the platform information associated with the instance with the specified digest. +func (l *list) SetOS(instanceDigest digest.Digest, os string) error { + docker, err := l.findDocker(instanceDigest) + if err != nil { + return err + } + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return err + } + docker.Platform.OS = os + oci.Platform.OS = os + return nil +} + +// OS retrieves the OS field in the platform information associated with the instance with the specified digest. +func (l *list) OS(instanceDigest digest.Digest) (string, error) { + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return "", err + } + return oci.Platform.OS, nil +} + +// SetArchitecture sets the Architecture field in the platform information associated with the instance with the specified digest. +func (l *list) SetArchitecture(instanceDigest digest.Digest, arch string) error { + docker, err := l.findDocker(instanceDigest) + if err != nil { + return err + } + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return err + } + docker.Platform.Architecture = arch + oci.Platform.Architecture = arch + return nil +} + +// Architecture retrieves the Architecture field in the platform information associated with the instance with the specified digest. +func (l *list) Architecture(instanceDigest digest.Digest) (string, error) { + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return "", err + } + return oci.Platform.Architecture, nil +} + +// SetOSVersion sets the OSVersion field in the platform information associated with the instance with the specified digest. +func (l *list) SetOSVersion(instanceDigest digest.Digest, osVersion string) error { + docker, err := l.findDocker(instanceDigest) + if err != nil { + return err + } + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return err + } + docker.Platform.OSVersion = osVersion + oci.Platform.OSVersion = osVersion + return nil +} + +// OSVersion retrieves the OSVersion field in the platform information associated with the instance with the specified digest. +func (l *list) OSVersion(instanceDigest digest.Digest) (string, error) { + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return "", err + } + return oci.Platform.OSVersion, nil +} + +// SetVariant sets the Variant field in the platform information associated with the instance with the specified digest. +func (l *list) SetVariant(instanceDigest digest.Digest, variant string) error { + docker, err := l.findDocker(instanceDigest) + if err != nil { + return err + } + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return err + } + docker.Platform.Variant = variant + oci.Platform.Variant = variant + return nil +} + +// Variant retrieves the Variant field in the platform information associated with the instance with the specified digest. +func (l *list) Variant(instanceDigest digest.Digest) (string, error) { + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return "", err + } + return oci.Platform.Variant, nil +} + +// SetFeatures sets the features list in the platform information associated with the instance with the specified digest. +// The field is specific to the Docker manifest list format, and is not present in OCI's image indexes. +func (l *list) SetFeatures(instanceDigest digest.Digest, features []string) error { + docker, err := l.findDocker(instanceDigest) + if err != nil { + return err + } + docker.Platform.Features = append([]string{}, features...) + // no OCI equivalent + return nil +} + +// Features retrieves the features list from the platform information associated with the instance with the specified digest. +// The field is specific to the Docker manifest list format, and is not present in OCI's image indexes. +func (l *list) Features(instanceDigest digest.Digest) ([]string, error) { + docker, err := l.findDocker(instanceDigest) + if err != nil { + return nil, err + } + return append([]string{}, docker.Platform.Features...), nil +} + +// SetOSFeatures sets the OS features list in the platform information associated with the instance with the specified digest. +func (l *list) SetOSFeatures(instanceDigest digest.Digest, osFeatures []string) error { + docker, err := l.findDocker(instanceDigest) + if err != nil { + return err + } + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return err + } + docker.Platform.OSFeatures = append([]string{}, osFeatures...) + oci.Platform.OSFeatures = append([]string{}, osFeatures...) + return nil +} + +// OSFeatures retrieves the OS features list from the platform information associated with the instance with the specified digest. +func (l *list) OSFeatures(instanceDigest digest.Digest) ([]string, error) { + oci, err := l.findOCIv1(instanceDigest) + if err != nil { + return nil, err + } + return append([]string{}, oci.Platform.OSFeatures...), nil +} + +// FromBlob builds a list from an encoded manifest list or image index. +func FromBlob(manifestBytes []byte) (List, error) { + manifestType := manifest.GuessMIMEType(manifestBytes) + list := &list{ + docker: manifest.Schema2List{ + SchemaVersion: 2, + MediaType: manifest.DockerV2ListMediaType, + }, + oci: v1.Index{ + Versioned: imgspec.Versioned{SchemaVersion: 2}, + }, + } + switch manifestType { + default: + return nil, errors.Wrapf(ErrManifestTypeNotSupported, "unable to load manifest list: unsupported format %q", manifestType) + case manifest.DockerV2ListMediaType: + if err := json.Unmarshal(manifestBytes, &list.docker); err != nil { + return nil, errors.Wrapf(err, "unable to parse Docker manifest list from image") + } + for _, m := range list.docker.Manifests { + list.oci.Manifests = append(list.oci.Manifests, v1.Descriptor{ + MediaType: m.Schema2Descriptor.MediaType, + Size: m.Schema2Descriptor.Size, + Digest: m.Schema2Descriptor.Digest, + Platform: &v1.Platform{ + Architecture: m.Platform.Architecture, + OS: m.Platform.OS, + OSVersion: m.Platform.OSVersion, + OSFeatures: m.Platform.OSFeatures, + Variant: m.Platform.Variant, + }, + }) + } + case v1.MediaTypeImageIndex: + if err := json.Unmarshal(manifestBytes, &list.oci); err != nil { + return nil, errors.Wrapf(err, "unable to parse OCIv1 manifest list") + } + for _, m := range list.oci.Manifests { + platform := m.Platform + if platform == nil { + platform = &v1.Platform{} + } + list.docker.Manifests = append(list.docker.Manifests, manifest.Schema2ManifestDescriptor{ + Schema2Descriptor: manifest.Schema2Descriptor{ + MediaType: m.MediaType, + Size: m.Size, + Digest: m.Digest, + }, + Platform: manifest.Schema2PlatformSpec{ + Architecture: platform.Architecture, + OS: platform.OS, + OSVersion: platform.OSVersion, + OSFeatures: platform.OSFeatures, + Variant: platform.Variant, + }, + }) + } + } + return list, nil +} + +func (l *list) preferOCI() bool { + // If we have any data that's only in the OCI format, use that. + for _, m := range l.oci.Manifests { + if len(m.URLs) > 0 { + return true + } + if len(m.Annotations) > 0 { + return true + } + } + // If we have any data that's only in the Docker format, use that. + for _, m := range l.docker.Manifests { + if len(m.Platform.Features) > 0 { + return false + } + } + // If we have no manifests, remember that the Docker format is + // explicitly typed, so use that. Otherwise, default to using the OCI + // format. + return len(l.docker.Manifests) != 0 +} + +// Serialize encodes the list using the specified format, or by selecting one +// which it thinks is appropriate. +func (l *list) Serialize(mimeType string) ([]byte, error) { + var manifestBytes []byte + switch mimeType { + case "": + if l.preferOCI() { + manifest, err := json.Marshal(&l.oci) + if err != nil { + return nil, errors.Wrapf(err, "error marshalling OCI image index") + } + manifestBytes = manifest + } else { + manifest, err := json.Marshal(&l.docker) + if err != nil { + return nil, errors.Wrapf(err, "error marshalling Docker manifest list") + } + manifestBytes = manifest + } + case v1.MediaTypeImageIndex: + manifest, err := json.Marshal(&l.oci) + if err != nil { + return nil, errors.Wrapf(err, "error marshalling OCI image index") + } + manifestBytes = manifest + case manifest.DockerV2ListMediaType: + manifest, err := json.Marshal(&l.docker) + if err != nil { + return nil, errors.Wrapf(err, "error marshalling Docker manifest list") + } + manifestBytes = manifest + default: + return nil, errors.Wrapf(ErrManifestTypeNotSupported, "serializing list to type %q not implemented", mimeType) + } + return manifestBytes, nil +} + +// Instances returns the list of image instances mentioned in this list. +func (l *list) Instances() []digest.Digest { + instances := make([]digest.Digest, 0, len(l.oci.Manifests)) + for _, instance := range l.oci.Manifests { + instances = append(instances, instance.Digest) + } + return instances +} diff --git a/pkg/manifests/manifests_test.go b/pkg/manifests/manifests_test.go new file mode 100644 index 00000000000..1f160c384e1 --- /dev/null +++ b/pkg/manifests/manifests_test.go @@ -0,0 +1,369 @@ +package manifests + +import ( + "io/ioutil" + "os" + "reflect" + "testing" + + "github.com/containers/image/v5/manifest" + "github.com/containers/storage/pkg/reexec" + digest "github.com/opencontainers/go-digest" + v1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +const ( + expectedInstance = digest.Digest("sha256:c829b1810d2dbb456e74a695fd3847530c8319e5a95dca623e9f1b1b89020d8b") + ociFixture = "testdata/fedora.index.json" + dockerFixture = "testdata/fedora.list.json" +) + +var ( + _ List = &list{} +) + +func TestMain(m *testing.M) { + if reexec.Init() { + return + } + os.Exit(m.Run()) +} + +func TestCreate(t *testing.T) { + list := Create() + if list == nil { + t.Fatalf("error creating an empty list") + } +} + +func TestFromBlob(t *testing.T) { + for _, version := range []string{ + ociFixture, + dockerFixture, + } { + bytes, err := ioutil.ReadFile(version) + if err != nil { + t.Fatalf("error loading %s: %v", version, err) + } + list, err := FromBlob(bytes) + if err != nil { + t.Fatalf("error parsing %s: %v", version, err) + } + if len(list.Docker().Manifests) != len(list.OCIv1().Manifests) { + t.Fatalf("%s: expected the same number of manifests, but %d != %d", version, len(list.Docker().Manifests), len(list.OCIv1().Manifests)) + } + for i := range list.Docker().Manifests { + d := list.Docker().Manifests[i] + o := list.OCIv1().Manifests[i] + if d.Platform.OS != o.Platform.OS { + t.Fatalf("%s: expected the same OS", version) + } + if d.Platform.Architecture != o.Platform.Architecture { + t.Fatalf("%s: expected the same Architecture", version) + } + } + } +} + +func TestAddInstance(t *testing.T) { + manifestBytes, err := ioutil.ReadFile("testdata/fedora-minimal.schema2.json") + if err != nil { + t.Fatalf("error loading testdata/fedora-minimal.schema2.json: %v", err) + } + manifestType := manifest.GuessMIMEType(manifestBytes) + manifestDigest, err := manifest.Digest(manifestBytes) + if err != nil { + t.Fatalf("error digesting testdata/fedora-minimal.schema2.json: %v", err) + } + for _, version := range []string{ + ociFixture, + dockerFixture, + } { + bytes, err := ioutil.ReadFile(version) + if err != nil { + t.Fatalf("error loading %s: %v", version, err) + } + list, err := FromBlob(bytes) + if err != nil { + t.Fatalf("error parsing %s: %v", version, err) + } + if err = list.AddInstance(manifestDigest, int64(len(manifestBytes)), manifestType, "linux", "amd64", "", nil, "", nil, nil); err != nil { + t.Fatalf("adding an instance failed in %s: %v", version, err) + } + if d, err := list.findDocker(manifestDigest); d == nil || err != nil { + t.Fatalf("adding an instance failed in %s: %v", version, err) + } + if o, err := list.findOCIv1(manifestDigest); o == nil || err != nil { + t.Fatalf("adding an instance failed in %s: %v", version, err) + } + } +} + +func TestRemove(t *testing.T) { + bytes, err := ioutil.ReadFile(ociFixture) + if err != nil { + t.Fatalf("error loading blob: %v", err) + } + list, err := FromBlob(bytes) + if err != nil { + t.Fatalf("error parsing blob: %v", err) + } + before := len(list.OCIv1().Manifests) + instanceDigest := expectedInstance + if d, err := list.findDocker(instanceDigest); d == nil || err != nil { + t.Fatalf("finding expected instance failed: %v", err) + } + if o, err := list.findOCIv1(instanceDigest); o == nil || err != nil { + t.Fatalf("finding expected instance failed: %v", err) + } + err = list.Remove(instanceDigest) + if err != nil { + t.Fatalf("error parsing blob: %v", err) + } + after := len(list.Docker().Manifests) + if after != before-1 { + t.Fatalf("removing instance should have succeeded") + } + if d, err := list.findDocker(instanceDigest); d != nil || err == nil { + t.Fatalf("finding instance should have failed") + } + if o, err := list.findOCIv1(instanceDigest); o != nil || err == nil { + t.Fatalf("finding instance should have failed") + } +} + +func testString(t *testing.T, values []string, set func(List, digest.Digest, string) error, get func(List, digest.Digest) (string, error)) { + bytes, err := ioutil.ReadFile(ociFixture) + if err != nil { + t.Fatalf("error loading blob: %v", err) + } + list, err := FromBlob(bytes) + if err != nil { + t.Fatalf("error parsing blob: %v", err) + } + for _, testString := range values { + if err = set(list, expectedInstance, testString); err != nil { + t.Fatalf("error setting %q: %v", testString, err) + } + b, err := list.Serialize("") + if err != nil { + t.Fatalf("error serializing list: %v", err) + } + list, err := FromBlob(b) + if err != nil { + t.Fatalf("error parsing list: %v", err) + } + value, err := get(list, expectedInstance) + if err != nil { + t.Fatalf("error retrieving value %q: %v", testString, err) + } + if value != testString { + t.Fatalf("expected value %q, got %q: %v", value, testString, err) + } + } +} + +func testStringSlice(t *testing.T, values [][]string, set func(List, digest.Digest, []string) error, get func(List, digest.Digest) ([]string, error)) { + bytes, err := ioutil.ReadFile(ociFixture) + if err != nil { + t.Fatalf("error loading blob: %v", err) + } + list, err := FromBlob(bytes) + if err != nil { + t.Fatalf("error parsing blob: %v", err) + } + for _, testSlice := range values { + if err = set(list, expectedInstance, testSlice); err != nil { + t.Fatalf("error setting %v: %v", testSlice, err) + } + b, err := list.Serialize("") + if err != nil { + t.Fatalf("error serializing list: %v", err) + } + list, err := FromBlob(b) + if err != nil { + t.Fatalf("error parsing list: %v", err) + } + values, err := get(list, expectedInstance) + if err != nil { + t.Fatalf("error retrieving value %v: %v", testSlice, err) + } + if !reflect.DeepEqual(values, testSlice) { + t.Fatalf("expected values %v, got %v: %v", testSlice, values, err) + } + } +} + +func testMap(t *testing.T, values []map[string]string, set func(List, *digest.Digest, map[string]string) error, get func(List, *digest.Digest) (map[string]string, error)) { + bytes, err := ioutil.ReadFile(ociFixture) + if err != nil { + t.Fatalf("error loading blob: %v", err) + } + list, err := FromBlob(bytes) + if err != nil { + t.Fatalf("error parsing blob: %v", err) + } + instance := expectedInstance + for _, instanceDigest := range []*digest.Digest{nil, &instance} { + for _, testMap := range values { + if err = set(list, instanceDigest, testMap); err != nil { + t.Fatalf("error setting %v: %v", testMap, err) + } + b, err := list.Serialize("") + if err != nil { + t.Fatalf("error serializing list: %v", err) + } + list, err := FromBlob(b) + if err != nil { + t.Fatalf("error parsing list: %v", err) + } + values, err := get(list, instanceDigest) + if err != nil { + t.Fatalf("error retrieving value %v: %v", testMap, err) + } + if len(values) != len(testMap) { + t.Fatalf("expected %d map entries, got %d", len(testMap), len(values)) + } + for k, v := range testMap { + if values[k] != v { + t.Fatalf("expected map value %q=%q, got %q", k, v, values[k]) + } + } + } + } +} + +func TestAnnotations(t *testing.T) { + testMap(t, + []map[string]string{{"A": "B", "C": "D"}, {"E": "F", "G": "H"}}, + func(l List, i *digest.Digest, m map[string]string) error { + return l.SetAnnotations(i, m) + }, + func(l List, i *digest.Digest) (map[string]string, error) { + return l.Annotations(i) + }, + ) +} + +func TestArchitecture(t *testing.T) { + testString(t, + []string{"abacus", "sliderule"}, + func(l List, i digest.Digest, s string) error { + return l.SetArchitecture(i, s) + }, + func(l List, i digest.Digest) (string, error) { + return l.Architecture(i) + }, + ) +} + +func TestFeatures(t *testing.T) { + testStringSlice(t, + [][]string{{"chrome", "hubcaps"}, {"climate", "control"}}, + func(l List, i digest.Digest, s []string) error { + return l.SetFeatures(i, s) + }, + func(l List, i digest.Digest) ([]string, error) { + return l.Features(i) + }, + ) +} + +func TestOS(t *testing.T) { + testString(t, + []string{"linux", "darwin"}, + func(l List, i digest.Digest, s string) error { + return l.SetOS(i, s) + }, + func(l List, i digest.Digest) (string, error) { + return l.OS(i) + }, + ) +} + +func TestOSFeatures(t *testing.T) { + testStringSlice(t, + [][]string{{"ipv6", "containers"}, {"nested", "virtualization"}}, + func(l List, i digest.Digest, s []string) error { + return l.SetOSFeatures(i, s) + }, + func(l List, i digest.Digest) ([]string, error) { + return l.OSFeatures(i) + }, + ) +} + +func TestOSVersion(t *testing.T) { + testString(t, + []string{"el7", "el8"}, + func(l List, i digest.Digest, s string) error { + return l.SetOSVersion(i, s) + }, + func(l List, i digest.Digest) (string, error) { + return l.OSVersion(i) + }, + ) +} + +func TestURLs(t *testing.T) { + testStringSlice(t, + [][]string{{"https://example.com", "https://example.net"}, {"http://example.com", "http://example.net"}}, + func(l List, i digest.Digest, s []string) error { + return l.SetURLs(i, s) + }, + func(l List, i digest.Digest) ([]string, error) { + return l.URLs(i) + }, + ) +} + +func TestVariant(t *testing.T) { + testString(t, + []string{"workstation", "cloud", "server"}, + func(l List, i digest.Digest, s string) error { + return l.SetVariant(i, s) + }, + func(l List, i digest.Digest) (string, error) { + return l.Variant(i) + }, + ) +} + +func TestSerialize(t *testing.T) { + for _, version := range []string{ + ociFixture, + dockerFixture, + } { + bytes, err := ioutil.ReadFile(version) + if err != nil { + t.Fatalf("error loading %s: %v", version, err) + } + list, err := FromBlob(bytes) + if err != nil { + t.Fatalf("error parsing %s: %v", version, err) + } + for _, mimeType := range []string{"", v1.MediaTypeImageIndex, manifest.DockerV2ListMediaType} { + b, err := list.Serialize(mimeType) + if err != nil { + t.Fatalf("error serializing %s with type %q: %v", version, mimeType, err) + } + l, err := FromBlob(b) + if err != nil { + t.Fatalf("error parsing %s re-encoded as %q: %v\n%s", version, mimeType, err, string(b)) + } + if !reflect.DeepEqual(list.Docker().Manifests, l.Docker().Manifests) { + t.Fatalf("re-encoded %s as %q was different\n%#v\n%#v", version, mimeType, list, l) + } + for i := range list.OCIv1().Manifests { + manifest := list.OCIv1().Manifests[i] + m := l.OCIv1().Manifests[i] + if manifest.Digest != m.Digest || + manifest.MediaType != m.MediaType || + manifest.Size != m.Size || + !reflect.DeepEqual(list.OCIv1().Manifests[i].Platform, l.OCIv1().Manifests[i].Platform) { + t.Fatalf("re-encoded %s OCI %d as %q was different\n%#v\n%#v", version, i, mimeType, list, l) + } + } + } + } +} diff --git a/pkg/manifests/testdata/fedora-minimal.schema2.json b/pkg/manifests/testdata/fedora-minimal.schema2.json new file mode 100644 index 00000000000..4d294837f7a --- /dev/null +++ b/pkg/manifests/testdata/fedora-minimal.schema2.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 1316, + "digest": "sha256:847a6054047619b8908f61e0211e3480ab20ce4b6cf17b03db081322ade301d3" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 42163967, + "digest": "sha256:8fad33c002fa130aceec4fd0cadc8bad0d7561667ab24aee75dea825be933de0" + } + ] +} diff --git a/pkg/manifests/testdata/fedora.index.json b/pkg/manifests/testdata/fedora.index.json new file mode 100644 index 00000000000..503f80d0404 --- /dev/null +++ b/pkg/manifests/testdata/fedora.index.json @@ -0,0 +1,45 @@ +{ + "manifests": [ + { + "digest": "sha256:f81f09918379d5442d20dff82a298f29698197035e737f76e511d5af422cabd7", + "mediaType": "application/vnd.oci.image.manifest.v1+json", + "platform": { + "architecture": "amd64", + "os": "linux" + }, + "size": 529 + }, + { + "digest": "sha256:c829b1810d2dbb456e74a695fd3847530c8319e5a95dca623e9f1b1b89020d8b", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "arm64", + "os": "linux", + "variant": "v8" + }, + "size": 529 + }, + { + "digest": "sha256:68b26da78d8790df143479ec2e3174c57cedb1c2e84ce1b2675d942d6848f2da", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "ppc64le", + "os": "linux" + }, + "size": 529 + }, + { + "digest": "sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "s390x", + "os": "linux" + }, + "size": 529 + } + ], + "schemaVersion": 2, + "annotations": { + "foo": "bar" + } +} diff --git a/pkg/manifests/testdata/fedora.list.json b/pkg/manifests/testdata/fedora.list.json new file mode 100644 index 00000000000..11898ba3754 --- /dev/null +++ b/pkg/manifests/testdata/fedora.list.json @@ -0,0 +1,43 @@ +{ + "manifests": [ + { + "digest": "sha256:f81f09918379d5442d20dff82a298f29698197035e737f76e511d5af422cabd7", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "amd64", + "os": "linux" + }, + "size": 529 + }, + { + "digest": "sha256:c829b1810d2dbb456e74a695fd3847530c8319e5a95dca623e9f1b1b89020d8b", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "arm64", + "os": "linux", + "variant": "v8" + }, + "size": 529 + }, + { + "digest": "sha256:68b26da78d8790df143479ec2e3174c57cedb1c2e84ce1b2675d942d6848f2da", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "ppc64le", + "os": "linux" + }, + "size": 529 + }, + { + "digest": "sha256:15352d97781ffdf357bf3459c037be3efac4133dc9070c2dce7eca7c05c3e736", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "platform": { + "architecture": "s390x", + "os": "linux" + }, + "size": 529 + } + ], + "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "schemaVersion": 2 +} diff --git a/pkg/manifests/testdata/fedora.schema2.json b/pkg/manifests/testdata/fedora.schema2.json new file mode 100644 index 00000000000..43cd2bb3f8d --- /dev/null +++ b/pkg/manifests/testdata/fedora.schema2.json @@ -0,0 +1,16 @@ +{ + "schemaVersion": 2, + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", + "config": { + "mediaType": "application/vnd.docker.container.image.v1+json", + "size": 2037, + "digest": "sha256:e9ed59d2baf72308f3a811ebc49ff3f4e0175abf40bf636bea0160759c637999" + }, + "layers": [ + { + "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip", + "size": 69532283, + "digest": "sha256:5a915a173fbc36dc8e1410afdd9de2b08f71efb226f8eb1ebcdc00a1acbced62" + } + ] +} diff --git a/pkg/parse/parse.go b/pkg/parse/parse.go index f0996315ae7..9194ddf58ed 100644 --- a/pkg/parse/parse.go +++ b/pkg/parse/parse.go @@ -14,7 +14,7 @@ import ( "unicode" "github.com/containers/buildah" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" "github.com/containers/storage/pkg/idtools" units "github.com/docker/go-units" specs "github.com/opencontainers/runtime-spec/specs-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/pkg/supplemented/errors.go b/pkg/supplemented/errors.go new file mode 100644 index 00000000000..6de679b50ac --- /dev/null +++ b/pkg/supplemented/errors.go @@ -0,0 +1,17 @@ +package supplemented + +import ( + "errors" + + "github.com/containers/buildah/pkg/manifests" +) + +var ( + // ErrDigestNotFound is returned when we look for an image instance + // with a particular digest in a list or index, and fail to find it. + ErrDigestNotFound = manifests.ErrDigestNotFound + // ErrBlobNotFound is returned when try to figure out which supplemental + // image we should ask for a blob with the specified characteristics, + // based on the information in each of the supplemental images' manifests. + ErrBlobNotFound = errors.New("location of blob could not be determined") +) diff --git a/pkg/supplemented/supplemented.go b/pkg/supplemented/supplemented.go new file mode 100644 index 00000000000..9dd47a0e2a9 --- /dev/null +++ b/pkg/supplemented/supplemented.go @@ -0,0 +1,393 @@ +package supplemented + +import ( + "container/list" + "context" + "io" + + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" + multierror "github.com/hashicorp/go-multierror" + digest "github.com/opencontainers/go-digest" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// supplementedImageReference groups multiple references together. +type supplementedImageReference struct { + types.ImageReference + references []types.ImageReference + multiple cp.ImageListSelection + instances []digest.Digest +} + +// supplementedImageSource represents an image, plus all of the blobs of other images. +type supplementedImageSource struct { + types.ImageSource + reference types.ImageReference + manifest []byte // The manifest list or image index. + manifestType string // The MIME type of the manifest list or image index. + sourceDefaultInstances map[types.ImageSource]digest.Digest // The default manifest instances of open ImageSource objects. + sourceInstancesByInstance map[digest.Digest]types.ImageSource // A map from manifest instance digests to open ImageSource objects. + instancesByBlobDigest map[digest.Digest]digest.Digest // A map from blob digests to manifest instance digests. +} + +// Reference groups one reference and some number of additional references +// together as a group. The first reference's default instance will be treated +// as the default instance of the resulting reference, with the other +// references' instances made available as instances for their respective +// digests. +func Reference(ref types.ImageReference, supplemental []types.ImageReference, multiple cp.ImageListSelection, instances []digest.Digest) types.ImageReference { + if len(instances) > 0 { + i := make([]digest.Digest, len(instances)) + copy(i, instances) + instances = i + } + return &supplementedImageReference{ + ImageReference: ref, + references: append([]types.ImageReference{}, supplemental...), + multiple: multiple, + instances: instances, + } +} + +// NewImage returns a new higher-level view of the image. +func (s *supplementedImageReference) NewImage(ctx context.Context, sys *types.SystemContext) (types.ImageCloser, error) { + src, err := s.NewImageSource(ctx, sys) + if err != nil { + return nil, errors.Wrapf(err, "error building a new Image using an ImageSource") + } + return image.FromSource(ctx, sys, src) +} + +// NewImageSource opens the referenced images, scans their manifests for +// instances, and builds mappings from each blob mentioned in them to their +// instances. +func (s *supplementedImageReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (iss types.ImageSource, err error) { + sources := make(map[digest.Digest]types.ImageSource) + defaultInstances := make(map[types.ImageSource]digest.Digest) + instances := make(map[digest.Digest]digest.Digest) + var sis *supplementedImageSource + + // Open the default instance for reading. + top, err := s.ImageReference.NewImageSource(ctx, sys) + if err != nil { + return nil, errors.Wrapf(err, "error opening %q as image source", transports.ImageName(s.ImageReference)) + } + + defer func() { + if err != nil { + if iss != nil { + // The composite source has been created. Use its Close method. + if err2 := iss.Close(); err2 != nil { + logrus.Errorf("error opening image: %v", err2) + } + } else if top != nil { + // The composite source has not been created, but the top was already opened. Close it. + if err2 := top.Close(); err2 != nil { + logrus.Errorf("error opening image: %v", err2) + } + } + } + }() + + var addSingle, addMulti func(manifestBytes []byte, manifestType string, src types.ImageSource) error + type manifestToRead struct { + src types.ImageSource + instance *digest.Digest + } + manifestsToRead := list.New() + + addSingle = func(manifestBytes []byte, manifestType string, src types.ImageSource) error { + // Mark this instance as being associated with this ImageSource. + manifestDigest, err := manifest.Digest(manifestBytes) + if err != nil { + return errors.Wrapf(err, "error computing digest over manifest %q", string(manifestBytes)) + } + sources[manifestDigest] = src + + // Parse the manifest as a single image. + man, err := manifest.FromBlob(manifestBytes, manifestType) + if err != nil { + return errors.Wrapf(err, "error parsing manifest %q", string(manifestBytes)) + } + + // Log the config blob's digest and the blobs of its layers as associated with this manifest. + config := man.ConfigInfo() + if config.Digest != "" { + instances[config.Digest] = manifestDigest + logrus.Debugf("blob %q belongs to %q", config.Digest, manifestDigest) + } + + layers := man.LayerInfos() + for _, layer := range layers { + instances[layer.Digest] = manifestDigest + logrus.Debugf("layer %q belongs to %q", layer.Digest, manifestDigest) + } + + return nil + } + + addMulti = func(manifestBytes []byte, manifestType string, src types.ImageSource) error { + // Mark this instance as being associated with this ImageSource. + manifestDigest, err := manifest.Digest(manifestBytes) + if err != nil { + return errors.Wrapf(err, "error computing manifest digest") + } + sources[manifestDigest] = src + + // Parse the manifest as a list of images. + list, err := manifest.ListFromBlob(manifestBytes, manifestType) + if err != nil { + return errors.Wrapf(err, "error parsing manifest blob %q as a %q", string(manifestBytes), manifestType) + } + + // Figure out which of its instances we want to look at. + var chaseInstances []digest.Digest + switch s.multiple { + case cp.CopySystemImage: + instance, err := list.ChooseInstance(sys) + if err != nil { + return errors.Wrapf(err, "error selecting appropriate instance from list") + } + chaseInstances = []digest.Digest{instance} + case cp.CopySpecificImages: + chaseInstances = s.instances + case cp.CopyAllImages: + chaseInstances = list.Instances() + } + + // Queue these manifest instances for reading from this + // ImageSource later, if we don't stumble across them somewhere + // else first. + for _, instanceIterator := range chaseInstances { + instance := instanceIterator + next := &manifestToRead{ + src: src, + instance: &instance, + } + if src == top { + // Prefer any other source. + manifestsToRead.PushBack(next) + } else { + // Prefer this source over the first ("main") one. + manifestsToRead.PushFront(next) + } + } + return nil + } + + visitedReferences := make(map[types.ImageReference]struct{}) + for i, ref := range append([]types.ImageReference{s.ImageReference}, s.references...) { + if _, visited := visitedReferences[ref]; visited { + continue + } + visitedReferences[ref] = struct{}{} + + // Open this image for reading. + var src types.ImageSource + if ref == s.ImageReference { + src = top + } else { + src, err = ref.NewImageSource(ctx, sys) + if err != nil { + return nil, errors.Wrapf(err, "error opening %q as image source", transports.ImageName(ref)) + } + } + + // Read the default manifest for the image. + manifestBytes, manifestType, err := src.GetManifest(ctx, nil) + if err != nil { + return nil, errors.Wrapf(err, "error reading default manifest from image %q", transports.ImageName(ref)) + } + + // If this is the first image, mark it as our starting point. + if i == 0 { + sources[""] = src + + sis = &supplementedImageSource{ + ImageSource: top, + reference: s, + manifest: manifestBytes, + manifestType: manifestType, + sourceDefaultInstances: defaultInstances, + sourceInstancesByInstance: sources, + instancesByBlobDigest: instances, + } + iss = sis + } + + // Record the digest of the ImageSource's default instance's manifest. + manifestDigest, err := manifest.Digest(manifestBytes) + if err != nil { + return nil, errors.Wrapf(err, "error computing digest of manifest from image %q", transports.ImageName(ref)) + } + sis.sourceDefaultInstances[src] = manifestDigest + + // If the ImageSource's default manifest is a list, parse each of its instances. + if manifest.MIMETypeIsMultiImage(manifestType) { + if err = addMulti(manifestBytes, manifestType, src); err != nil { + return nil, errors.Wrapf(err, "error adding multi-image %q", transports.ImageName(ref)) + } + } else { + if err = addSingle(manifestBytes, manifestType, src); err != nil { + return nil, errors.Wrapf(err, "error adding single image %q", transports.ImageName(ref)) + } + } + } + + // Parse the rest of the instances. + for manifestsToRead.Front() != nil { + front := manifestsToRead.Front() + value := front.Value + manifestToRead, ok := value.(*manifestToRead) + if !ok { + panic("bug: wrong type looking for *manifestToRead in list?") + } + manifestsToRead.Remove(front) + + // If we already read this manifest, no need to read it again. + if _, alreadyRead := sources[*manifestToRead.instance]; alreadyRead { + continue + } + + // Read the instance's manifest. + manifestBytes, manifestType, err := manifestToRead.src.GetManifest(ctx, manifestToRead.instance) + if err != nil { + // if errors.Cause(err) == storage.ErrImageUnknown || os.IsNotExist(errors.Cause(err)) { + // Trust that we either don't need it, or that it's in another reference. + // continue + // } + return nil, errors.Wrapf(err, "error reading manifest for instance %q", manifestToRead.instance) + } + + if manifest.MIMETypeIsMultiImage(manifestType) { + // Add the list's contents. + if err = addMulti(manifestBytes, manifestType, manifestToRead.src); err != nil { + return nil, errors.Wrapf(err, "error adding single image instance %q", manifestToRead.instance) + } + } else { + // Add the single image's contents. + if err = addSingle(manifestBytes, manifestType, manifestToRead.src); err != nil { + return nil, errors.Wrapf(err, "error adding single image instance %q", manifestToRead.instance) + } + } + } + + return iss, nil +} + +func (s *supplementedImageReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error { + return errors.Errorf("deletion of images not implemented") +} + +func (s *supplementedImageSource) Close() error { + var returnErr *multierror.Error + closed := make(map[types.ImageSource]struct{}) + for _, sourceInstance := range s.sourceInstancesByInstance { + if _, closed := closed[sourceInstance]; closed { + continue + } + if err := sourceInstance.Close(); err != nil { + returnErr = multierror.Append(returnErr, err) + } + closed[sourceInstance] = struct{}{} + } + return returnErr.ErrorOrNil() +} + +func (s *supplementedImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { + requestInstanceDigest := instanceDigest + if instanceDigest == nil { + return s.manifest, s.manifestType, nil + } + if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok { + if *instanceDigest == s.sourceDefaultInstances[sourceInstance] { + requestInstanceDigest = nil + } + return sourceInstance.GetManifest(ctx, requestInstanceDigest) + } + return nil, "", errors.Wrapf(ErrDigestNotFound, "error getting manifest for digest %q", *instanceDigest) +} + +func (s *supplementedImageSource) GetBlob(ctx context.Context, blob types.BlobInfo, bic types.BlobInfoCache) (io.ReadCloser, int64, error) { + sourceInstance, ok := s.instancesByBlobDigest[blob.Digest] + if !ok { + return nil, -1, errors.Wrapf(ErrBlobNotFound, "error blob %q in known instances", blob.Digest) + } + src, ok := s.sourceInstancesByInstance[sourceInstance] + if !ok { + return nil, -1, errors.Wrapf(ErrDigestNotFound, "error getting image source for instance %q", sourceInstance) + } + return src.GetBlob(ctx, blob, bic) +} + +func (s *supplementedImageSource) HasThreadSafeGetBlob() bool { + checked := make(map[types.ImageSource]struct{}) + for _, sourceInstance := range s.sourceInstancesByInstance { + if _, checked := checked[sourceInstance]; checked { + continue + } + if !sourceInstance.HasThreadSafeGetBlob() { + return false + } + checked[sourceInstance] = struct{}{} + } + return true +} + +func (s *supplementedImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { + var src types.ImageSource + requestInstanceDigest := instanceDigest + if instanceDigest == nil { + if sourceInstance, ok := s.sourceInstancesByInstance[""]; ok { + src = sourceInstance + } + } else { + if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok { + src = sourceInstance + } + if *instanceDigest == s.sourceDefaultInstances[src] { + requestInstanceDigest = nil + } + } + if src != nil { + return src.GetSignatures(ctx, requestInstanceDigest) + } + return nil, errors.Wrapf(ErrDigestNotFound, "error finding instance for instance digest %q to read signatures", *instanceDigest) +} + +func (s *supplementedImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) { + var src types.ImageSource + requestInstanceDigest := instanceDigest + if instanceDigest == nil { + if sourceInstance, ok := s.sourceInstancesByInstance[""]; ok { + src = sourceInstance + } + } else { + if sourceInstance, ok := s.sourceInstancesByInstance[*instanceDigest]; ok { + src = sourceInstance + } + if *instanceDigest == s.sourceDefaultInstances[src] { + requestInstanceDigest = nil + } + } + if src != nil { + blobInfos, err := src.LayerInfosForCopy(ctx, requestInstanceDigest) + if err != nil { + return nil, errors.Wrapf(err, "error reading layer infos for copy from instance %q", instanceDigest) + } + var manifestDigest digest.Digest + if instanceDigest != nil { + manifestDigest = *instanceDigest + } + for _, blobInfo := range blobInfos { + s.instancesByBlobDigest[blobInfo.Digest] = manifestDigest + } + return blobInfos, nil + } + return nil, errors.Wrapf(ErrDigestNotFound, "error finding instance for instance digest %q to copy layers", *instanceDigest) +} diff --git a/pkg/supplemented/supplemented_test.go b/pkg/supplemented/supplemented_test.go new file mode 100644 index 00000000000..40a56d07216 --- /dev/null +++ b/pkg/supplemented/supplemented_test.go @@ -0,0 +1,384 @@ +package supplemented + +import ( + "archive/tar" + "bytes" + "context" + "crypto/rand" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "os" + "runtime" + "testing" + "time" + + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/signature" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + digest "github.com/opencontainers/go-digest" + specs "github.com/opencontainers/image-spec/specs-go" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/stretchr/testify/assert" +) + +var ( + _ types.ImageReference = &supplementedImageReference{} + _ types.ImageSource = &supplementedImageSource{} + now = time.Now() +) + +type nullbic struct { +} + +func (b *nullbic) UncompressedDigest(anyDigest digest.Digest) digest.Digest { + return "" +} +func (b *nullbic) RecordDigestUncompressedPair(anyDigest digest.Digest, uncompressed digest.Digest) { +} +func (b *nullbic) RecordKnownLocation(transport types.ImageTransport, scope types.BICTransportScope, digest digest.Digest, location types.BICLocationReference) { +} +func (b *nullbic) CandidateLocations(transport types.ImageTransport, scope types.BICTransportScope, digest digest.Digest, canSubstitute bool) []types.BICReplacementCandidate { + return nil +} + +func makeLayer(t *testing.T) []byte { + var b bytes.Buffer + len := 512 + randomLen := 8 + tw := tar.NewWriter(&b) + assert.Nilf(t, tw.WriteHeader(&tar.Header{ + Typeflag: tar.TypeReg, + Name: "tmpfile", + Size: int64(len), + Mode: 0644, + Uname: "root", + Gname: "root", + ModTime: time.Now(), + }), "error writing in-memory layer") + buf := make([]byte, len) + n, err := rand.Read(buf[0:randomLen]) + assert.Nilf(t, err, "error reading a random byte") + assert.Equalf(t, randomLen, n, "error reading random content: wrong length") + for i := randomLen; i < len; i++ { + buf[i] = (buf[i-1] + 1) & 0xff + } + n, err = tw.Write(buf) + assert.Nilf(t, err, "error writing file content") + assert.Equalf(t, n, len, "error writing file content: wrong length") + assert.Nilf(t, tw.Close(), "error flushing file content") + return b.Bytes() +} + +func makeConfig(arch, os string, layer []byte) v1.Image { + diffID := digest.Canonical.FromBytes(layer) + return v1.Image{ + Created: &now, + Architecture: arch, + OS: os, + Config: v1.ImageConfig{ + User: "root", + Entrypoint: []string{"/tmpfile"}, + WorkingDir: "/", + }, + RootFS: v1.RootFS{ + Type: "layers", + DiffIDs: []digest.Digest{diffID}, + }, + History: []v1.History{{ + Created: &now, + CreatedBy: "shenanigans", + }}, + } +} + +func makeManifest(layer, config []byte) v1.Manifest { + return v1.Manifest{ + Versioned: specs.Versioned{ + SchemaVersion: 2, + }, + Config: v1.Descriptor{ + MediaType: v1.MediaTypeImageConfig, + Digest: digest.Canonical.FromBytes(config), + Size: int64(len(config)), + }, + Layers: []v1.Descriptor{{ + MediaType: v1.MediaTypeImageLayer, + Digest: digest.Canonical.FromBytes(layer), + Size: int64(len(layer)), + }}, + } +} + +func makeImage(t *testing.T, arch, os string) (ref types.ImageReference, dir string, layer, config, manifest []byte) { + ctx := context.TODO() + + dir, err := ioutil.TempDir("", "supplemented") + assert.Nilf(t, err, "error creating temporary directory") + + layerBytes := makeLayer(t) + cb := makeConfig(arch, os, layer) + configBytes, err := json.Marshal(&cb) + assert.Nilf(t, err, "error encoding image configuration") + m := makeManifest(layerBytes, configBytes) + manifestBytes, err := json.Marshal(&m) + assert.Nilf(t, err, "error encoding image manifest") + + ref, err = alltransports.ParseImageName(fmt.Sprintf("dir:%s", dir)) + assert.Nilf(t, err, "error parsing reference 'dir:%s'", dir) + sys := &types.SystemContext{} + dest, err := ref.NewImageDestination(ctx, sys) + assert.Nilf(t, err, "error opening 'dir:%s' as an image destination", dir) + bi := types.BlobInfo{ + MediaType: v1.MediaTypeImageLayer, + Digest: digest.Canonical.FromBytes(layerBytes), + Size: int64(len(layerBytes)), + } + _, err = dest.PutBlob(ctx, bytes.NewReader(layerBytes), bi, &nullbic{}, false) + assert.Nilf(t, err, "error storing layer blob to 'dir:%s'", dir) + bi = types.BlobInfo{ + MediaType: v1.MediaTypeImageConfig, + Digest: digest.Canonical.FromBytes(configBytes), + Size: int64(len(configBytes)), + } + _, err = dest.PutBlob(ctx, bytes.NewReader(configBytes), bi, &nullbic{}, true) + assert.Nilf(t, err, "error storing config blob to 'dir:%s'", dir) + err = dest.PutManifest(ctx, manifestBytes, nil) + assert.Nilf(t, err, "error storing manifest to 'dir:%s'", dir) + err = dest.Commit(ctx, nil) + assert.Nilf(t, err, "error committing image to 'dir:%s'", dir) + + return ref, dir, layerBytes, configBytes, manifestBytes +} + +func TestSupplemented(t *testing.T) { + ctx := context.TODO() + arch2 := "foo" + arch3 := "bar" + + sys := &types.SystemContext{ + SignaturePolicyPath: "../../tests/policy.json", + } + defaultPolicy, err := signature.DefaultPolicy(sys) + assert.Nilf(t, err, "error obtaining default policy") + policyContext, err := signature.NewPolicyContext(defaultPolicy) + assert.Nilf(t, err, "error obtaining policy context") + + ref1, dir1, layer1, config1, manifest1 := makeImage(t, runtime.GOARCH, runtime.GOOS) + defer os.RemoveAll(dir1) + digest1, err := manifest.Digest(manifest1) + assert.Nilf(t, err, "error digesting manifest") + + ref2, dir2, layer2, config2, manifest2 := makeImage(t, arch2, runtime.GOOS) + defer os.RemoveAll(dir2) + digest2, err := manifest.Digest(manifest2) + assert.Nilf(t, err, "error digesting manifest") + + ref3, dir3, layer3, config3, manifest3 := makeImage(t, arch3, runtime.GOOS) + defer os.RemoveAll(dir3) + digest3, err := manifest.Digest(manifest3) + assert.Nilf(t, err, "error digesting manifest") + + multidir, err := ioutil.TempDir("", "supplemented") + assert.Nilf(t, err, "error creating temporary directory") + defer os.RemoveAll(multidir) + + destDir, err := ioutil.TempDir("", "supplemented") + assert.Nilf(t, err, "error creating temporary directory") + defer os.RemoveAll(destDir) + + index := v1.Index{ + Versioned: specs.Versioned{ + SchemaVersion: 2, + }, + Manifests: []v1.Descriptor{ + { + MediaType: v1.MediaTypeImageManifest, + Digest: digest1, + Size: int64(len(manifest1)), + Platform: &v1.Platform{ + Architecture: runtime.GOARCH, + OS: runtime.GOOS, + }, + }, + { + MediaType: v1.MediaTypeImageManifest, + Digest: digest2, + Size: int64(len(manifest2)), + Platform: &v1.Platform{ + Architecture: arch2, + OS: runtime.GOOS, + }, + }, + { + MediaType: v1.MediaTypeImageManifest, + Digest: digest3, + Size: int64(len(manifest3)), + Platform: &v1.Platform{ + Architecture: arch3, + OS: runtime.GOOS, + }, + }, + }, + } + indexBytes, err := json.Marshal(&index) + assert.Nilf(t, err, "error encoding image index") + indexDigest, err := manifest.Digest(indexBytes) + assert.Nilf(t, err, "error digesting image index") + + destRef, err := alltransports.ParseImageName(fmt.Sprintf("dir:%s", destDir)) + assert.Nilf(t, err, "error parsing reference 'dir:%s'", destDir) + + multiRef, err := alltransports.ParseImageName(fmt.Sprintf("dir:%s", multidir)) + assert.Nilf(t, err, "error parsing reference 'dir:%s'", multidir) + destImg, err := multiRef.NewImageDestination(ctx, sys) + assert.Nilf(t, err, "error opening 'dir:%s' as an image destination", multidir) + err = destImg.PutManifest(ctx, indexBytes, nil) + assert.Nilf(t, err, "error storing index to 'dir:%s'", multidir) + err = destImg.Commit(ctx, nil) + assert.Nilf(t, err, "error committing image to 'dir:%s'", multidir) + + t.Logf("list: digest=%q,value=%s", indexDigest, string(indexBytes)) + + _, err = multiRef.NewImage(ctx, sys) + assert.NotNilf(t, err, "unexpected success opening image 'dir:%s': shouldn't have been able to read config", multidir) + + src, err := Reference(multiRef, []types.ImageReference{ref1}, cp.CopyAllImages, nil).NewImageSource(ctx, sys) + assert.NotNilf(t, err, "unexpected success opening image 'dir:%s': shouldn't have been able to read all manifests", multidir) + assert.Nilf(t, src, "unexpected success opening image 'dir:%s': shouldn't have been able to read all manifests", multidir) + src, err = Reference(multiRef, []types.ImageReference{ref1}, cp.CopySpecificImages, []digest.Digest{digest1}).NewImageSource(ctx, sys) + assert.Nilf(t, err, "error opening image 'dir:%s' with specific instances", multidir) + assert.Nilf(t, src.Close(), "error closing image 'dir:%s' with specific instances", multidir) + + img, err := Reference(multiRef, nil, cp.CopySystemImage, nil).NewImage(ctx, sys) + assert.NotNilf(t, err, "unexpected success opening image 'dir:%s': shouldn't have been able to read config", multidir) + assert.Nilf(t, img, "unexpected success opening image 'dir:%s': shouldn't have been able to read config", multidir) + img, err = Reference(multiRef, []types.ImageReference{ref1}, cp.CopySystemImage, []digest.Digest{digest1}).NewImage(ctx, sys) + assert.Nilf(t, err, "error opening image %q+%q", transports.ImageName(multiRef), transports.ImageName(ref1)) + assert.Nilf(t, img.Close(), "error closing image %q+%q", transports.ImageName(multiRef), transports.ImageName(ref1)) + + type testCase struct { + label string + supplements []types.ImageReference + expectToFind [][]byte + expectToNotFind [][]byte + multiple cp.ImageListSelection + instances []digest.Digest + } + + for _, test := range []testCase{ + { + label: "no supplements, nil instances", + supplements: nil, + expectToFind: nil, + expectToNotFind: [][]byte{layer1, config1, layer2, config2, layer3, config3}, + multiple: cp.CopySpecificImages, + instances: nil, + }, + { + label: "no supplements, 0 instances", + supplements: nil, + expectToFind: nil, + expectToNotFind: [][]byte{layer1, config1, layer2, config2, layer3, config3}, + multiple: cp.CopySpecificImages, + instances: []digest.Digest{}, + }, + { + label: "just ref1 supplementing", + supplements: []types.ImageReference{ref1}, + expectToFind: [][]byte{layer1, config1}, + expectToNotFind: [][]byte{layer2, config2, layer3, config3}, + multiple: cp.CopySpecificImages, + instances: []digest.Digest{digest1}, + }, + { + label: "just ref2 supplementing", + supplements: []types.ImageReference{ref2}, + expectToFind: [][]byte{layer2, config2}, + expectToNotFind: [][]byte{layer1, config1, layer3, config3}, + multiple: cp.CopySpecificImages, + instances: []digest.Digest{digest2}, + }, + { + label: "just ref3 supplementing", + supplements: []types.ImageReference{ref3}, + expectToFind: [][]byte{layer3, config3}, + expectToNotFind: [][]byte{layer1, config1, layer2, config2}, + multiple: cp.CopySpecificImages, + instances: []digest.Digest{digest3}, + }, + { + label: "refs 1 and 2 supplementing", + supplements: []types.ImageReference{ref1, ref2}, + expectToFind: [][]byte{layer1, config1, layer2, config2}, + expectToNotFind: [][]byte{layer3, config3}, + multiple: cp.CopySpecificImages, + instances: []digest.Digest{digest1, digest2}, + }, + { + label: "refs 2 and 3 supplementing", + supplements: []types.ImageReference{ref2, ref3}, + expectToFind: [][]byte{layer2, config2, layer3, config3}, + expectToNotFind: [][]byte{layer1, config1}, + multiple: cp.CopySpecificImages, + instances: []digest.Digest{digest2, digest3}, + }, + { + label: "refs 1 and 3 supplementing", + supplements: []types.ImageReference{ref1, ref3}, + expectToFind: [][]byte{layer1, config1, layer3, config3}, + expectToNotFind: [][]byte{layer2, config2}, + multiple: cp.CopySpecificImages, + instances: []digest.Digest{digest1, digest3}, + }, + { + label: "all refs supplementing, all instances", + supplements: []types.ImageReference{ref1, ref2, ref3}, + expectToFind: [][]byte{layer1, config1, layer2, config2, layer3, config3}, + expectToNotFind: nil, + multiple: cp.CopySpecificImages, + instances: []digest.Digest{digest1, digest2, digest3}, + }, + { + label: "all refs supplementing, all images", + supplements: []types.ImageReference{ref1, ref2, ref3}, + expectToFind: [][]byte{layer1, config1, layer2, config2, layer3, config3}, + expectToNotFind: nil, + multiple: cp.CopyAllImages, + }, + } { + supplemented := Reference(multiRef, test.supplements, test.multiple, test.instances) + src, err := supplemented.NewImageSource(ctx, sys) + assert.Nilf(t, err, "error opening image source 'dir:%s'[%s]", multidir, test.label) + defer src.Close() + for i, expect := range test.expectToFind { + bi := types.BlobInfo{ + Digest: digest.Canonical.FromBytes(expect), + Size: int64(len(expect)), + } + rc, _, err := src.GetBlob(ctx, bi, &nullbic{}) + assert.Nilf(t, err, "error reading blob 'dir:%s'[%s][%d]", multidir, test.label, i) + _, err = io.Copy(ioutil.Discard, rc) + assert.Nilf(t, err, "error discarding blob 'dir:%s'[%s][%d]", multidir, test.label, i) + rc.Close() + } + for i, expect := range test.expectToNotFind { + bi := types.BlobInfo{ + Digest: digest.Canonical.FromBytes(expect), + Size: int64(len(expect)), + } + _, _, err := src.GetBlob(ctx, bi, &nullbic{}) + assert.NotNilf(t, err, "unexpected success reading blob 'dir:%s'[%s][%d]", multidir, test.label, i) + } + options := cp.Options{ + ImageListSelection: test.multiple, + Instances: test.instances, + } + _, err = cp.Image(ctx, policyContext, destRef, supplemented, &options) + assert.Nilf(t, err, "error copying image 'dir:%s'[%s]", multidir, test.label) + } +} diff --git a/pull.go b/pull.go index 60dc3693f94..300f3b3969c 100644 --- a/pull.go +++ b/pull.go @@ -8,18 +8,18 @@ import ( "github.com/containers/buildah/pkg/blobcache" "github.com/containers/buildah/util" - cp "github.com/containers/image/v4/copy" - "github.com/containers/image/v4/directory" - "github.com/containers/image/v4/docker" - dockerarchive "github.com/containers/image/v4/docker/archive" - "github.com/containers/image/v4/docker/reference" - tarfile "github.com/containers/image/v4/docker/tarfile" - ociarchive "github.com/containers/image/v4/oci/archive" - oci "github.com/containers/image/v4/oci/layout" - "github.com/containers/image/v4/signature" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + cp "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/directory" + "github.com/containers/image/v5/docker" + dockerarchive "github.com/containers/image/v5/docker/archive" + "github.com/containers/image/v5/docker/reference" + tarfile "github.com/containers/image/v5/docker/tarfile" + ociarchive "github.com/containers/image/v5/oci/archive" + oci "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/signature" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/containers/storage" multierror "github.com/hashicorp/go-multierror" "github.com/pkg/errors" diff --git a/tests/e2e/buildah_suite_test.go b/tests/e2e/buildah_suite_test.go index 13b183a007d..a443371cbbc 100644 --- a/tests/e2e/buildah_suite_test.go +++ b/tests/e2e/buildah_suite_test.go @@ -13,11 +13,11 @@ import ( "encoding/json" "github.com/containers/buildah" - "github.com/containers/image/v4/copy" - "github.com/containers/image/v4/signature" - "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/transports/alltransports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/copy" + "github.com/containers/image/v5/signature" + "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" sstorage "github.com/containers/storage" "github.com/containers/storage/pkg/reexec" . "github.com/onsi/ginkgo" diff --git a/tests/imgtype/imgtype.go b/tests/imgtype/imgtype.go index dee0de190e5..5366d0d7d7d 100644 --- a/tests/imgtype/imgtype.go +++ b/tests/imgtype/imgtype.go @@ -11,10 +11,10 @@ import ( "github.com/containers/buildah" "github.com/containers/buildah/docker" "github.com/containers/buildah/util" - "github.com/containers/image/v4/manifest" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/transports/alltransports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/manifest" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" "github.com/containers/storage" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/sirupsen/logrus" 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 diff --git a/util.go b/util.go index 06492fad20a..44db18b4546 100644 --- a/util.go +++ b/util.go @@ -8,9 +8,9 @@ import ( "path/filepath" "github.com/containers/buildah/util" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/pkg/sysregistriesv2" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/pkg/sysregistriesv2" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chrootarchive" diff --git a/util/util.go b/util/util.go index a572d140500..d5e842315f1 100644 --- a/util/util.go +++ b/util/util.go @@ -10,12 +10,12 @@ import ( "syscall" "github.com/containers/buildah/pkg/cgroups" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/pkg/sysregistriesv2" - "github.com/containers/image/v4/signature" - is "github.com/containers/image/v4/storage" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/pkg/sysregistriesv2" + "github.com/containers/image/v5/signature" + is "github.com/containers/image/v5/storage" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/docker/distribution/registry/api/errcode" specs "github.com/opencontainers/runtime-spec/specs-go" diff --git a/vendor/github.com/containers/image/v4/image/docker_list.go b/vendor/github.com/containers/image/v4/image/docker_list.go deleted file mode 100644 index a11cd06b991..00000000000 --- a/vendor/github.com/containers/image/v4/image/docker_list.go +++ /dev/null @@ -1,94 +0,0 @@ -package image - -import ( - "context" - "encoding/json" - "fmt" - "runtime" - - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" - "github.com/opencontainers/go-digest" - "github.com/pkg/errors" -) - -type platformSpec struct { - Architecture string `json:"architecture"` - OS string `json:"os"` - OSVersion string `json:"os.version,omitempty"` - OSFeatures []string `json:"os.features,omitempty"` - Variant string `json:"variant,omitempty"` - Features []string `json:"features,omitempty"` // removed in OCI -} - -// A manifestDescriptor references a platform-specific manifest. -type manifestDescriptor struct { - manifest.Schema2Descriptor - Platform platformSpec `json:"platform"` -} - -type manifestList struct { - SchemaVersion int `json:"schemaVersion"` - MediaType string `json:"mediaType"` - Manifests []manifestDescriptor `json:"manifests"` -} - -// chooseDigestFromManifestList parses blob as a schema2 manifest list, -// and returns the digest of the image appropriate for the current environment. -func chooseDigestFromManifestList(sys *types.SystemContext, blob []byte) (digest.Digest, error) { - wantedArch := runtime.GOARCH - if sys != nil && sys.ArchitectureChoice != "" { - wantedArch = sys.ArchitectureChoice - } - wantedOS := runtime.GOOS - if sys != nil && sys.OSChoice != "" { - wantedOS = sys.OSChoice - } - - list := manifestList{} - if err := json.Unmarshal(blob, &list); err != nil { - return "", err - } - for _, d := range list.Manifests { - if d.Platform.Architecture == wantedArch && d.Platform.OS == wantedOS { - return d.Digest, nil - } - } - return "", fmt.Errorf("no image found in manifest list for architecture %s, OS %s", wantedArch, wantedOS) -} - -func manifestSchema2FromManifestList(ctx context.Context, sys *types.SystemContext, src types.ImageSource, manblob []byte) (genericManifest, error) { - targetManifestDigest, err := chooseDigestFromManifestList(sys, manblob) - if err != nil { - return nil, err - } - manblob, mt, err := src.GetManifest(ctx, &targetManifestDigest) - if err != nil { - return nil, err - } - - matches, err := manifest.MatchesDigest(manblob, targetManifestDigest) - if err != nil { - return nil, errors.Wrap(err, "Error computing manifest digest") - } - if !matches { - return nil, errors.Errorf("Manifest image does not match selected manifest digest %s", targetManifestDigest) - } - - return manifestInstanceFromBlob(ctx, sys, src, manblob, mt) -} - -// ChooseManifestInstanceFromManifestList returns a digest of a manifest appropriate -// for the current system from the manifest available from src. -func ChooseManifestInstanceFromManifestList(ctx context.Context, sys *types.SystemContext, src types.UnparsedImage) (digest.Digest, error) { - // For now this only handles manifest.DockerV2ListMediaType; we can generalize it later, - // probably along with manifest list editing. - blob, mt, err := src.Manifest(ctx) - if err != nil { - return "", err - } - if mt != manifest.DockerV2ListMediaType { - return "", fmt.Errorf("Internal error: Trying to select an image from a non-manifest-list manifest type %s", mt) - } - return chooseDigestFromManifestList(sys, blob) -} diff --git a/vendor/github.com/containers/image/v4/LICENSE b/vendor/github.com/containers/image/v5/LICENSE similarity index 100% rename from vendor/github.com/containers/image/v4/LICENSE rename to vendor/github.com/containers/image/v5/LICENSE diff --git a/vendor/github.com/containers/image/v4/copy/copy.go b/vendor/github.com/containers/image/v5/copy/copy.go similarity index 72% rename from vendor/github.com/containers/image/v4/copy/copy.go rename to vendor/github.com/containers/image/v5/copy/copy.go index 30d8a446413..090d862d51c 100644 --- a/vendor/github.com/containers/image/v4/copy/copy.go +++ b/vendor/github.com/containers/image/v5/copy/copy.go @@ -13,15 +13,16 @@ import ( "sync" "time" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/image" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/pkg/blobinfocache" - "github.com/containers/image/v4/pkg/compression" - "github.com/containers/image/v4/signature" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/blobinfocache" + "github.com/containers/image/v5/pkg/compression" + "github.com/containers/image/v5/signature" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" digest "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/vbauerster/mpb" @@ -110,6 +111,37 @@ type imageCopier struct { canSubstituteBlobs bool } +const ( + // CopySystemImage is the default value which, when set in + // Options.ImageListSelection, indicates that the caller expects only one + // image to be copied, so if the source reference refers to a list of + // images, one that matches the current system will be selected. + CopySystemImage ImageListSelection = iota + // CopyAllImages is a value which, when set in Options.ImageListSelection, + // indicates that the caller expects to copy multiple images, and if + // the source reference refers to a list, that the list and every image + // to which it refers will be copied. If the source reference refers + // to a list, the target reference can not accept lists, an error + // should be returned. + CopyAllImages + // CopySpecificImages is a value which, when set in + // Options.ImageListSelection, indicates that the caller expects the + // source reference to be either a single image or a list of images, + // and if the source reference is a list, wants only specific instances + // from it copied (or none of them, if the list of instances to copy is + // empty), along with the list itself. If the target reference can + // only accept one image (i.e., it cannot accept lists), an error + // should be returned. + CopySpecificImages +) + +// ImageListSelection is one of CopySystemImage, CopyAllImages, or +// CopySpecificImages, to control whether, when the source reference is a list, +// copy.Image() copies only an image which matches the current runtime +// environment, or all images which match the supplied reference, or only +// specific images from the source reference. +type ImageListSelection int + // Options allows supplying non-default configuration modifying the behavior of CopyImage. type Options struct { RemoveSignatures bool // Remove any pre-existing signatures. SignBy will still add a new signature. @@ -121,12 +153,24 @@ type Options struct { Progress chan types.ProgressProperties // Reported to when ProgressInterval has arrived for a single artifact+offset. // manifest MIME type of image set by user. "" is default and means use the autodetection to the the manifest MIME type ForceManifestMIMEType string + ImageListSelection ImageListSelection // set to either CopySystemImage (the default), CopyAllImages, or CopySpecificImages to control which instances we copy when the source reference is a list; ignored if the source reference is not a list + Instances []digest.Digest // if ImageListSelection is CopySpecificImages, copy only these instances and the list itself +} + +// validateImageListSelection returns an error if the passed-in value is not one that we recognize as a valid ImageListSelection value +func validateImageListSelection(selection ImageListSelection) error { + switch selection { + case CopySystemImage, CopyAllImages, CopySpecificImages: + return nil + default: + return errors.Errorf("Invalid value for options.ImageListSelection: %d", selection) + } } // Image copies image from srcRef to destRef, using policyContext to validate // source image admissibility. It returns the manifest which was written to // the new copy of the image. -func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, srcRef types.ImageReference, options *Options) (manifest []byte, retErr error) { +func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, srcRef types.ImageReference, options *Options) (copiedManifest []byte, retErr error) { // NOTE this function uses an output parameter for the error return value. // Setting this and returning is the ideal way to return an error. // @@ -136,6 +180,10 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, options = &Options{} } + if err := validateImageListSelection(options.ImageListSelection); err != nil { + return nil, err + } + reportWriter := ioutil.Discard if options.ReportWriter != nil { @@ -206,79 +254,278 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, } if !multiImage { - // The simple case: Just copy a single image. - if manifest, err = c.copyOneImage(ctx, policyContext, options, unparsedToplevel); err != nil { + // The simple case: just copy a single image. + if copiedManifest, _, _, err = c.copyOneImage(ctx, policyContext, options, unparsedToplevel, unparsedToplevel, nil); err != nil { return nil, err } - } else { - // This is a manifest list. Choose a single image and copy it. - // FIXME: Copy to destinations which support manifest lists, one image at a time. - instanceDigest, err := image.ChooseManifestInstanceFromManifestList(ctx, options.SourceCtx, unparsedToplevel) + } else if options.ImageListSelection == CopySystemImage { + // This is a manifest list, and we weren't asked to copy multiple images. Choose a single image that + // matches the current system to copy, and copy it. + mfest, manifestType, err := unparsedToplevel.Manifest(ctx) + if err != nil { + return nil, errors.Wrapf(err, "Error reading manifest for %s", transports.ImageName(srcRef)) + } + manifestList, err := manifest.ListFromBlob(mfest, manifestType) + if err != nil { + return nil, errors.Wrapf(err, "Error parsing primary manifest as list for %s", transports.ImageName(srcRef)) + } + instanceDigest, err := manifestList.ChooseInstance(options.SourceCtx) // try to pick one that matches options.SourceCtx if err != nil { return nil, errors.Wrapf(err, "Error choosing an image from manifest list %s", transports.ImageName(srcRef)) } - logrus.Debugf("Source is a manifest list; copying (only) instance %s", instanceDigest) + logrus.Debugf("Source is a manifest list; copying (only) instance %s for current system", instanceDigest) unparsedInstance := image.UnparsedInstance(rawSource, &instanceDigest) - if manifest, err = c.copyOneImage(ctx, policyContext, options, unparsedInstance); err != nil { + if copiedManifest, _, _, err = c.copyOneImage(ctx, policyContext, options, unparsedToplevel, unparsedInstance, nil); err != nil { + return nil, err + } + } else { /* options.ImageListSelection == CopyAllImages or options.ImageListSelection == CopySpecificImages, */ + // If we were asked to copy multiple images and can't, that's an error. + if !supportsMultipleImages(c.dest) { + return nil, errors.Errorf("Error copying multiple images: destination transport %q does not support copying multiple images as a group", destRef.Transport().Name()) + } + // Copy some or all of the images. + switch options.ImageListSelection { + case CopyAllImages: + logrus.Debugf("Source is a manifest list; copying all instances") + case CopySpecificImages: + logrus.Debugf("Source is a manifest list; copying some instances") + } + if copiedManifest, _, err = c.copyMultipleImages(ctx, policyContext, options, unparsedToplevel); err != nil { return nil, err } } - if err := c.dest.Commit(ctx); err != nil { + if err := c.dest.Commit(ctx, unparsedToplevel); err != nil { return nil, errors.Wrap(err, "Error committing the finished image") } - return manifest, nil + return copiedManifest, nil } -// Image copies a single (on-manifest-list) image unparsedImage, using policyContext to validate +// Checks if the destination supports accepting multiple images by checking if it can support +// manifest types that are lists of other manifests. +func supportsMultipleImages(dest types.ImageDestination) bool { + mtypes := dest.SupportedManifestMIMETypes() + if len(mtypes) == 0 { + // Anything goes! + return true + } + for _, mtype := range mtypes { + if manifest.MIMETypeIsMultiImage(mtype) { + return true + } + } + return false +} + +// copyMultipleImages copies some or all of an image list's instances, using +// policyContext to validate source image admissibility. +func (c *copier) copyMultipleImages(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedToplevel *image.UnparsedImage) (copiedManifest []byte, copiedManifestType string, retErr error) { + // Parse the list and get a copy of the original value after it's re-encoded. + manifestList, manifestType, err := unparsedToplevel.Manifest(ctx) + if err != nil { + return nil, "", errors.Wrapf(err, "Error reading manifest list") + } + list, err := manifest.ListFromBlob(manifestList, manifestType) + if err != nil { + return nil, "", errors.Wrapf(err, "Error parsing manifest list %q", string(manifestList)) + } + originalList := list.Clone() + + // Read and/or clear the set of signatures for this list. + var sigs [][]byte + if options.RemoveSignatures { + sigs = [][]byte{} + } else { + c.Printf("Getting image list signatures\n") + s, err := c.rawSource.GetSignatures(ctx, nil) + if err != nil { + return nil, "", errors.Wrap(err, "Error reading signatures") + } + sigs = s + } + if len(sigs) != 0 { + c.Printf("Checking if image list destination supports signatures\n") + if err := c.dest.SupportsSignatures(ctx); err != nil { + return nil, "", errors.Wrap(err, "Can not copy signatures") + } + } + + // Determine if we'll need to convert the manifest list to a different format. + forceListMIMEType := options.ForceManifestMIMEType + switch forceListMIMEType { + case manifest.DockerV2Schema1MediaType, manifest.DockerV2Schema1SignedMediaType, manifest.DockerV2Schema2MediaType: + forceListMIMEType = manifest.DockerV2ListMediaType + case imgspecv1.MediaTypeImageManifest: + forceListMIMEType = imgspecv1.MediaTypeImageIndex + } + selectedListType, err := c.determineListConversion(manifestType, c.dest.SupportedManifestMIMETypes(), forceListMIMEType) + if err != nil { + return nil, "", errors.Wrapf(err, "Error determining manifest list type to write to destination") + } + if selectedListType != list.MIMEType() { + canModifyManifestList := (len(sigs) == 0) + if !canModifyManifestList { + return nil, "", errors.Errorf("Error: manifest list must be converted to type %q to be written to destination, but that would invalidate signatures", selectedListType) + } + } + + // Copy each image, or just the ones we want to copy, in turn. + instanceDigests := list.Instances() + imagesToCopy := len(instanceDigests) + if options.ImageListSelection == CopySpecificImages { + imagesToCopy = len(options.Instances) + } + c.Printf("Copying %d of %d images in list\n", imagesToCopy, len(instanceDigests)) + updates := make([]manifest.ListUpdate, len(instanceDigests)) + instancesCopied := 0 + for i, instanceDigest := range instanceDigests { + if options.ImageListSelection == CopySpecificImages { + skip := true + for _, instance := range options.Instances { + if instance == instanceDigest { + skip = false + break + } + } + if skip { + update, err := list.Instance(instanceDigest) + if err != nil { + return nil, "", err + } + logrus.Debugf("Skipping instance %s (%d/%d)", instanceDigest, i+1, len(instanceDigests)) + // Record the digest/size/type of the manifest that we didn't copy. + updates[i] = update + continue + } + } + logrus.Debugf("Copying instance %s (%d/%d)", instanceDigest, i+1, len(instanceDigests)) + c.Printf("Copying image %s (%d/%d)\n", instanceDigest, instancesCopied+1, imagesToCopy) + unparsedInstance := image.UnparsedInstance(c.rawSource, &instanceDigest) + updatedManifest, updatedManifestType, updatedManifestDigest, err := c.copyOneImage(ctx, policyContext, options, unparsedToplevel, unparsedInstance, &instanceDigest) + if err != nil { + return nil, "", err + } + instancesCopied++ + // Record the result of a possible conversion here. + update := manifest.ListUpdate{ + Digest: updatedManifestDigest, + Size: int64(len(updatedManifest)), + MediaType: updatedManifestType, + } + updates[i] = update + } + + // Now reset the digest/size/types of the manifests in the list to account for any conversions that we made. + if err = list.UpdateInstances(updates); err != nil { + return nil, "", errors.Wrapf(err, "Error updating manifest list") + } + + // Check if the updates meaningfully changed the list of images. + listIsModified := false + if !reflect.DeepEqual(list.Instances(), originalList.Instances()) { + listIsModified = true + } + + // Perform the list conversion. + if selectedListType != list.MIMEType() { + list, err = list.ConvertToMIMEType(selectedListType) + if err != nil { + return nil, "", errors.Wrapf(err, "Error converting manifest list to list with MIME type %q", selectedListType) + } + } + + // If we can't use the original value, but we have to change it, flag an error. + if listIsModified { + manifestList, err = list.Serialize() + if err != nil { + return nil, "", errors.Wrapf(err, "Error encoding updated manifest list (%q: %#v)", list.MIMEType(), list.Instances()) + } + logrus.Debugf("Manifest list has been updated") + } + + // Save the manifest list. + c.Printf("Writing manifest list to image destination\n") + if err = c.dest.PutManifest(ctx, manifestList, nil); err != nil { + return nil, "", errors.Wrapf(err, "Error writing manifest list %q", string(manifestList)) + } + + // Sign the manifest list. + if options.SignBy != "" { + newSig, err := c.createSignature(manifestList, options.SignBy) + if err != nil { + return nil, "", err + } + sigs = append(sigs, newSig) + } + + c.Printf("Storing list signatures\n") + if err := c.dest.PutSignatures(ctx, sigs, nil); err != nil { + return nil, "", errors.Wrap(err, "Error writing signatures") + } + + return manifestList, selectedListType, nil +} + +// copyOneImage copies a single (non-manifest-list) image unparsedImage, using policyContext to validate // source image admissibility. -func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedImage *image.UnparsedImage) (manifestBytes []byte, retErr error) { +func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.PolicyContext, options *Options, unparsedToplevel, unparsedImage *image.UnparsedImage, targetInstance *digest.Digest) (retManifest []byte, retManifestType string, retManifestDigest digest.Digest, retErr error) { // The caller is handling manifest lists; this could happen only if a manifest list contains a manifest list. // Make sure we fail cleanly in such cases. multiImage, err := isMultiImage(ctx, unparsedImage) if err != nil { // FIXME FIXME: How to name a reference for the sub-image? - return nil, errors.Wrapf(err, "Error determining manifest MIME type for %s", transports.ImageName(unparsedImage.Reference())) + return nil, "", "", errors.Wrapf(err, "Error determining manifest MIME type for %s", transports.ImageName(unparsedImage.Reference())) } if multiImage { - return nil, fmt.Errorf("Unexpectedly received a manifest list instead of a manifest for a single image") + return nil, "", "", fmt.Errorf("Unexpectedly received a manifest list instead of a manifest for a single image") } // Please keep this policy check BEFORE reading any other information about the image. - // (the multiImage check above only matches the MIME type, which we have received anyway. + // (The multiImage check above only matches the MIME type, which we have received anyway. // Actual parsing of anything should be deferred.) if allowed, err := policyContext.IsRunningImageAllowed(ctx, unparsedImage); !allowed || err != nil { // Be paranoid and fail if either return value indicates so. - return nil, errors.Wrap(err, "Source image rejected") + return nil, "", "", errors.Wrap(err, "Source image rejected") } src, err := image.FromUnparsedImage(ctx, options.SourceCtx, unparsedImage) if err != nil { - return nil, errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(c.rawSource.Reference())) + return nil, "", "", errors.Wrapf(err, "Error initializing image from source %s", transports.ImageName(c.rawSource.Reference())) } // If the destination is a digested reference, make a note of that, determine what digest value we're - // expecting, and check that the source manifest matches it. + // expecting, and check that the source manifest matches it. If the source manifest doesn't, but it's + // one item from a manifest list that matches it, accept that as a match. destIsDigestedReference := false if named := c.dest.Reference().DockerReference(); named != nil { if digested, ok := named.(reference.Digested); ok { destIsDigestedReference = true sourceManifest, _, err := src.Manifest(ctx) if err != nil { - return nil, errors.Wrapf(err, "Error reading manifest from source image") + return nil, "", "", errors.Wrapf(err, "Error reading manifest from source image") } matches, err := manifest.MatchesDigest(sourceManifest, digested.Digest()) if err != nil { - return nil, errors.Wrapf(err, "Error computing digest of source image's manifest") + return nil, "", "", errors.Wrapf(err, "Error computing digest of source image's manifest") } if !matches { - return nil, errors.New("Digest of source image's manifest would not match destination reference") + manifestList, _, err := unparsedToplevel.Manifest(ctx) + if err != nil { + return nil, "", "", errors.Wrapf(err, "Error reading manifest from source image") + } + matches, err = manifest.MatchesDigest(manifestList, digested.Digest()) + if err != nil { + return nil, "", "", errors.Wrapf(err, "Error computing digest of source image's manifest") + } + if !matches { + return nil, "", "", errors.New("Digest of source image's manifest would not match destination reference") + } } } } if err := checkImageDestinationForCurrentRuntimeOS(ctx, options.DestinationCtx, src, c.dest); err != nil { - return nil, err + return nil, "", "", err } var sigs [][]byte @@ -288,14 +535,14 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli c.Printf("Getting image source signatures\n") s, err := src.Signatures(ctx) if err != nil { - return nil, errors.Wrap(err, "Error reading signatures") + return nil, "", "", errors.Wrap(err, "Error reading signatures") } sigs = s } if len(sigs) != 0 { c.Printf("Checking if image destination supports signatures\n") if err := c.dest.SupportsSignatures(ctx); err != nil { - return nil, errors.Wrap(err, "Can not copy signatures") + return nil, "", "", errors.Wrap(err, "Can not copy signatures") } } @@ -315,28 +562,29 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli ic.canSubstituteBlobs = ic.canModifyManifest && options.SignBy == "" if err := ic.updateEmbeddedDockerReference(); err != nil { - return nil, err + return nil, "", "", err } // We compute preferredManifestMIMEType only to show it in error messages. // Without having to add this context in an error message, we would be happy enough to know only that no conversion is needed. preferredManifestMIMEType, otherManifestMIMETypeCandidates, err := ic.determineManifestConversion(ctx, c.dest.SupportedManifestMIMETypes(), options.ForceManifestMIMEType) if err != nil { - return nil, err + return nil, "", "", err } // If src.UpdatedImageNeedsLayerDiffIDs(ic.manifestUpdates) will be true, it needs to be true by the time we get here. ic.diffIDsAreNeeded = src.UpdatedImageNeedsLayerDiffIDs(*ic.manifestUpdates) if err := ic.copyLayers(ctx); err != nil { - return nil, err + return nil, "", "", err } // With docker/distribution registries we do not know whether the registry accepts schema2 or schema1 only; // and at least with the OpenShift registry "acceptschema2" option, there is no way to detect the support // without actually trying to upload something and getting a types.ManifestTypeRejectedError. // So, try the preferred manifest MIME type. If the process succeeds, fine… - manifestBytes, err = ic.copyUpdatedConfigAndManifest(ctx) + manifestBytes, retManifestDigest, err := ic.copyUpdatedConfigAndManifest(ctx, targetInstance) + retManifestType = preferredManifestMIMEType if err != nil { logrus.Debugf("Writing manifest using preferred type %s failed: %v", preferredManifestMIMEType, err) // … if it fails, _and_ the failure is because the manifest is rejected, we may have other options. @@ -344,14 +592,14 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli // We don’t have other options. // In principle the code below would handle this as well, but the resulting error message is fairly ugly. // Don’t bother the user with MIME types if we have no choice. - return nil, err + return nil, "", "", err } // If the original MIME type is acceptable, determineManifestConversion always uses it as preferredManifestMIMEType. // So if we are here, we will definitely be trying to convert the manifest. // With !ic.canModifyManifest, that would just be a string of repeated failures for the same reason, // so let’s bail out early and with a better error message. if !ic.canModifyManifest { - return nil, errors.Wrap(err, "Writing manifest failed (and converting it is not possible)") + return nil, "", "", errors.Wrap(err, "Writing manifest failed (and converting it is not possible)") } // errs is a list of errors when trying various manifest types. Also serves as an "upload succeeded" flag when set to nil. @@ -359,7 +607,7 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli for _, manifestMIMEType := range otherManifestMIMETypeCandidates { logrus.Debugf("Trying to use manifest type %s…", manifestMIMEType) ic.manifestUpdates.ManifestMIMEType = manifestMIMEType - attemptedManifest, err := ic.copyUpdatedConfigAndManifest(ctx) + attemptedManifest, attemptedManifestDigest, err := ic.copyUpdatedConfigAndManifest(ctx, targetInstance) if err != nil { logrus.Debugf("Upload of manifest type %s failed: %v", manifestMIMEType, err) errs = append(errs, fmt.Sprintf("%s(%v)", manifestMIMEType, err)) @@ -368,28 +616,30 @@ func (c *copier) copyOneImage(ctx context.Context, policyContext *signature.Poli // We have successfully uploaded a manifest. manifestBytes = attemptedManifest + retManifestDigest = attemptedManifestDigest + retManifestType = manifestMIMEType errs = nil // Mark this as a success so that we don't abort below. break } if errs != nil { - return nil, fmt.Errorf("Uploading manifest failed, attempted the following formats: %s", strings.Join(errs, ", ")) + return nil, "", "", fmt.Errorf("Uploading manifest failed, attempted the following formats: %s", strings.Join(errs, ", ")) } } if options.SignBy != "" { newSig, err := c.createSignature(manifestBytes, options.SignBy) if err != nil { - return nil, err + return nil, "", "", err } sigs = append(sigs, newSig) } c.Printf("Storing signatures\n") - if err := c.dest.PutSignatures(ctx, sigs); err != nil { - return nil, errors.Wrap(err, "Error writing signatures") + if err := c.dest.PutSignatures(ctx, sigs, targetInstance); err != nil { + return nil, "", "", errors.Wrap(err, "Error writing signatures") } - return manifestBytes, nil + return manifestBytes, retManifestType, retManifestDigest, nil } // Printf writes a formatted string to c.reportWriter. @@ -554,12 +804,13 @@ func layerDigestsDiffer(a, b []types.BlobInfo) bool { } // copyUpdatedConfigAndManifest updates the image per ic.manifestUpdates, if necessary, -// stores the resulting config and manifest to the destination, and returns the stored manifest. -func (ic *imageCopier) copyUpdatedConfigAndManifest(ctx context.Context) ([]byte, error) { +// stores the resulting config and manifest to the destination, and returns the stored manifest +// and its digest. +func (ic *imageCopier) copyUpdatedConfigAndManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, digest.Digest, error) { pendingImage := ic.src if !reflect.DeepEqual(*ic.manifestUpdates, types.ManifestUpdateOptions{InformationOnly: ic.manifestUpdates.InformationOnly}) { if !ic.canModifyManifest { - return nil, errors.Errorf("Internal error: copy needs an updated manifest but that was known to be forbidden") + return nil, "", errors.Errorf("Internal error: copy needs an updated manifest but that was known to be forbidden") } if !ic.diffIDsAreNeeded && ic.src.UpdatedImageNeedsLayerDiffIDs(*ic.manifestUpdates) { // We have set ic.diffIDsAreNeeded based on the preferred MIME type returned by determineManifestConversion. @@ -568,28 +819,35 @@ func (ic *imageCopier) copyUpdatedConfigAndManifest(ctx context.Context) ([]byte // when ic.c.dest.SupportedManifestMIMETypes() includes both s1 and s2, the upload using s1 failed, and we are now trying s2. // Supposedly s2-only registries do not exist or are extremely rare, so failing with this error message is good enough for now. // If handling such registries turns out to be necessary, we could compute ic.diffIDsAreNeeded based on the full list of manifest MIME type candidates. - return nil, errors.Errorf("Can not convert image to %s, preparing DiffIDs for this case is not supported", ic.manifestUpdates.ManifestMIMEType) + return nil, "", errors.Errorf("Can not convert image to %s, preparing DiffIDs for this case is not supported", ic.manifestUpdates.ManifestMIMEType) } pi, err := ic.src.UpdatedImage(ctx, *ic.manifestUpdates) if err != nil { - return nil, errors.Wrap(err, "Error creating an updated image manifest") + return nil, "", errors.Wrap(err, "Error creating an updated image manifest") } pendingImage = pi } - manifest, _, err := pendingImage.Manifest(ctx) + man, _, err := pendingImage.Manifest(ctx) if err != nil { - return nil, errors.Wrap(err, "Error reading manifest") + return nil, "", errors.Wrap(err, "Error reading manifest") } if err := ic.c.copyConfig(ctx, pendingImage); err != nil { - return nil, err + return nil, "", err } ic.c.Printf("Writing manifest to image destination\n") - if err := ic.c.dest.PutManifest(ctx, manifest); err != nil { - return nil, errors.Wrap(err, "Error writing manifest") + manifestDigest, err := manifest.Digest(man) + if err != nil { + return nil, "", err + } + if instanceDigest != nil { + instanceDigest = &manifestDigest + } + if err := ic.c.dest.PutManifest(ctx, man, instanceDigest); err != nil { + return nil, "", errors.Wrap(err, "Error writing manifest") } - return manifest, nil + return man, manifestDigest, nil } // newProgressPool creates a *mpb.Progress and a cleanup function. diff --git a/vendor/github.com/containers/image/v4/copy/manifest.go b/vendor/github.com/containers/image/v5/copy/manifest.go similarity index 75% rename from vendor/github.com/containers/image/v4/copy/manifest.go rename to vendor/github.com/containers/image/v5/copy/manifest.go index 7c981fcad22..f5f6c9c5fd4 100644 --- a/vendor/github.com/containers/image/v4/copy/manifest.go +++ b/vendor/github.com/containers/image/v5/copy/manifest.go @@ -4,8 +4,8 @@ import ( "context" "strings" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -119,3 +119,36 @@ func isMultiImage(ctx context.Context, img types.UnparsedImage) (bool, error) { } return manifest.MIMETypeIsMultiImage(mt), nil } + +// determineListConversion takes the current MIME type of a list of manifests, +// the list of MIME types supported for a given destination, and a possible +// forced value, and returns the MIME type to which we should convert the list +// of manifests, whether we are converting to it or using it unmodified. +func (c *copier) determineListConversion(currentListMIMEType string, destSupportedMIMETypes []string, forcedListMIMEType string) (string, error) { + // If we're forcing it, we prefer the forced value over everything else. + if forcedListMIMEType != "" { + return forcedListMIMEType, nil + } + // If there's no list of supported types, then anything we support is expected to be supported. + if len(destSupportedMIMETypes) == 0 { + destSupportedMIMETypes = manifest.SupportedListMIMETypes + } + var selectedType string + for i := range destSupportedMIMETypes { + // The second priority is the first member of the list of acceptable types that is a list, + // but keep going in case current type occurs later in the list. + if selectedType == "" && manifest.MIMETypeIsMultiImage(destSupportedMIMETypes[i]) { + selectedType = destSupportedMIMETypes[i] + } + // The first priority is the current type, if it's in the list, since that lets us avoid a + // conversion that isn't strictly necessary. + if destSupportedMIMETypes[i] == currentListMIMEType { + selectedType = destSupportedMIMETypes[i] + } + } + if selectedType == "" { + return "", errors.Errorf("destination does not support any supported manifest list types (%v)", manifest.SupportedListMIMETypes) + } + // Done. + return selectedType, nil +} diff --git a/vendor/github.com/containers/image/v4/copy/progress_reader.go b/vendor/github.com/containers/image/v5/copy/progress_reader.go similarity index 93% rename from vendor/github.com/containers/image/v4/copy/progress_reader.go rename to vendor/github.com/containers/image/v5/copy/progress_reader.go index c6a1e3da694..1d0c41bcea1 100644 --- a/vendor/github.com/containers/image/v4/copy/progress_reader.go +++ b/vendor/github.com/containers/image/v5/copy/progress_reader.go @@ -4,7 +4,7 @@ import ( "io" "time" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" ) // progressReader is a reader that reports its progress on an interval. diff --git a/vendor/github.com/containers/image/v4/copy/sign.go b/vendor/github.com/containers/image/v5/copy/sign.go similarity index 91% rename from vendor/github.com/containers/image/v4/copy/sign.go rename to vendor/github.com/containers/image/v5/copy/sign.go index 64c3b4b2be7..8f46e9de693 100644 --- a/vendor/github.com/containers/image/v4/copy/sign.go +++ b/vendor/github.com/containers/image/v5/copy/sign.go @@ -1,8 +1,8 @@ package copy import ( - "github.com/containers/image/v4/signature" - "github.com/containers/image/v4/transports" + "github.com/containers/image/v5/signature" + "github.com/containers/image/v5/transports" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containers/image/v4/directory/directory_dest.go b/vendor/github.com/containers/image/v5/directory/directory_dest.go similarity index 89% rename from vendor/github.com/containers/image/v4/directory/directory_dest.go rename to vendor/github.com/containers/image/v5/directory/directory_dest.go index 18f7dde70de..2d6650de710 100644 --- a/vendor/github.com/containers/image/v4/directory/directory_dest.go +++ b/vendor/github.com/containers/image/v5/directory/directory_dest.go @@ -7,7 +7,7 @@ import ( "os" "path/filepath" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -199,16 +199,23 @@ func (d *dirImageDestination) TryReusingBlob(ctx context.Context, info types.Blo } // PutManifest writes manifest to the destination. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to write the manifest for (when +// the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list. +// It is expected but not enforced that the instanceDigest, when specified, matches the digest of `manifest` as generated +// by `manifest.Digest()`. // FIXME? This should also receive a MIME type if known, to differentiate between schema versions. // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema), // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError. -func (d *dirImageDestination) PutManifest(ctx context.Context, manifest []byte) error { - return ioutil.WriteFile(d.ref.manifestPath(), manifest, 0644) +func (d *dirImageDestination) PutManifest(ctx context.Context, manifest []byte, instanceDigest *digest.Digest) error { + return ioutil.WriteFile(d.ref.manifestPath(instanceDigest), manifest, 0644) } -func (d *dirImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error { +// PutSignatures writes a set of signatures to the destination. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to write or overwrite the signatures for +// (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list. +func (d *dirImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error { for i, sig := range signatures { - if err := ioutil.WriteFile(d.ref.signaturePath(i), sig, 0644); err != nil { + if err := ioutil.WriteFile(d.ref.signaturePath(i, instanceDigest), sig, 0644); err != nil { return err } } @@ -219,7 +226,7 @@ func (d *dirImageDestination) PutSignatures(ctx context.Context, signatures [][] // WARNING: This does not have any transactional semantics: // - Uploaded data MAY be visible to others before Commit() is called // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed) -func (d *dirImageDestination) Commit(ctx context.Context) error { +func (d *dirImageDestination) Commit(context.Context, types.UnparsedImage) error { return nil } diff --git a/vendor/github.com/containers/image/v4/directory/directory_src.go b/vendor/github.com/containers/image/v5/directory/directory_src.go similarity index 74% rename from vendor/github.com/containers/image/v4/directory/directory_src.go rename to vendor/github.com/containers/image/v5/directory/directory_src.go index 921c1941cca..ad9129d4012 100644 --- a/vendor/github.com/containers/image/v4/directory/directory_src.go +++ b/vendor/github.com/containers/image/v5/directory/directory_src.go @@ -6,10 +6,9 @@ import ( "io/ioutil" "os" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" - "github.com/pkg/errors" ) type dirImageSource struct { @@ -38,10 +37,7 @@ func (s *dirImageSource) Close() error { // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list); // this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists). func (s *dirImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { - if instanceDigest != nil { - return nil, "", errors.Errorf(`Getting target manifest not supported by "dir:"`) - } - m, err := ioutil.ReadFile(s.ref.manifestPath()) + m, err := ioutil.ReadFile(s.ref.manifestPath(instanceDigest)) if err != nil { return nil, "", err } @@ -73,12 +69,9 @@ func (s *dirImageSource) GetBlob(ctx context.Context, info types.BlobInfo, cache // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list // (e.g. if the source never returns manifest lists). func (s *dirImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { - if instanceDigest != nil { - return nil, errors.Errorf(`Manifests lists are not supported by "dir:"`) - } signatures := [][]byte{} for i := 0; ; i++ { - signature, err := ioutil.ReadFile(s.ref.signaturePath(i)) + signature, err := ioutil.ReadFile(s.ref.signaturePath(i, instanceDigest)) if err != nil { if os.IsNotExist(err) { break @@ -90,7 +83,14 @@ func (s *dirImageSource) GetSignatures(ctx context.Context, instanceDigest *dige return signatures, nil } -// LayerInfosForCopy() returns updated layer info that should be used when copying, in preference to values in the manifest, if specified. -func (s *dirImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { +// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer +// blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob() +// to read the image's layers. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for +// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list +// (e.g. if the source never returns manifest lists). +// The Digest field is guaranteed to be provided; Size may be -1. +// WARNING: The list may contain duplicates, and they are semantically relevant. +func (s *dirImageSource) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) { return nil, nil } diff --git a/vendor/github.com/containers/image/v4/directory/directory_transport.go b/vendor/github.com/containers/image/v5/directory/directory_transport.go similarity index 92% rename from vendor/github.com/containers/image/v4/directory/directory_transport.go rename to vendor/github.com/containers/image/v5/directory/directory_transport.go index 29ac7115f30..adfec6ef373 100644 --- a/vendor/github.com/containers/image/v4/directory/directory_transport.go +++ b/vendor/github.com/containers/image/v5/directory/directory_transport.go @@ -6,11 +6,11 @@ import ( "path/filepath" "strings" - "github.com/containers/image/v4/directory/explicitfilepath" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/image" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/directory/explicitfilepath" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -166,18 +166,24 @@ func (ref dirReference) DeleteImage(ctx context.Context, sys *types.SystemContex } // manifestPath returns a path for the manifest within a directory using our conventions. -func (ref dirReference) manifestPath() string { +func (ref dirReference) manifestPath(instanceDigest *digest.Digest) string { + if instanceDigest != nil { + return filepath.Join(ref.path, instanceDigest.Encoded()+".manifest.json") + } return filepath.Join(ref.path, "manifest.json") } // layerPath returns a path for a layer tarball within a directory using our conventions. func (ref dirReference) layerPath(digest digest.Digest) string { // FIXME: Should we keep the digest identification? - return filepath.Join(ref.path, digest.Hex()) + return filepath.Join(ref.path, digest.Encoded()) } // signaturePath returns a path for a signature within a directory using our conventions. -func (ref dirReference) signaturePath(index int) string { +func (ref dirReference) signaturePath(index int, instanceDigest *digest.Digest) string { + if instanceDigest != nil { + return filepath.Join(ref.path, fmt.Sprintf(instanceDigest.Encoded()+".signature-%d", index+1)) + } return filepath.Join(ref.path, fmt.Sprintf("signature-%d", index+1)) } diff --git a/vendor/github.com/containers/image/v4/directory/explicitfilepath/path.go b/vendor/github.com/containers/image/v5/directory/explicitfilepath/path.go similarity index 100% rename from vendor/github.com/containers/image/v4/directory/explicitfilepath/path.go rename to vendor/github.com/containers/image/v5/directory/explicitfilepath/path.go diff --git a/vendor/github.com/containers/image/v4/docker/archive/dest.go b/vendor/github.com/containers/image/v5/docker/archive/dest.go similarity index 92% rename from vendor/github.com/containers/image/v4/docker/archive/dest.go rename to vendor/github.com/containers/image/v5/docker/archive/dest.go index 9e06e7c96a4..5845f63be11 100644 --- a/vendor/github.com/containers/image/v4/docker/archive/dest.go +++ b/vendor/github.com/containers/image/v5/docker/archive/dest.go @@ -5,8 +5,8 @@ import ( "io" "os" - "github.com/containers/image/v4/docker/tarfile" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/tarfile" + "github.com/containers/image/v5/types" "github.com/pkg/errors" ) @@ -67,6 +67,6 @@ func (d *archiveImageDestination) Close() error { // WARNING: This does not have any transactional semantics: // - Uploaded data MAY be visible to others before Commit() is called // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed) -func (d *archiveImageDestination) Commit(ctx context.Context) error { +func (d *archiveImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error { return d.Destination.Commit(ctx) } diff --git a/vendor/github.com/containers/image/v4/docker/archive/src.go b/vendor/github.com/containers/image/v5/docker/archive/src.go similarity index 74% rename from vendor/github.com/containers/image/v4/docker/archive/src.go rename to vendor/github.com/containers/image/v5/docker/archive/src.go index feea0decd57..a90707437c5 100644 --- a/vendor/github.com/containers/image/v4/docker/archive/src.go +++ b/vendor/github.com/containers/image/v5/docker/archive/src.go @@ -2,8 +2,8 @@ package archive import ( "context" - "github.com/containers/image/v4/docker/tarfile" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/tarfile" + "github.com/containers/image/v5/types" "github.com/sirupsen/logrus" ) @@ -33,8 +33,3 @@ func newImageSource(ctx context.Context, ref archiveReference) (types.ImageSourc func (s *archiveImageSource) Reference() types.ImageReference { return s.ref } - -// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified. -func (s *archiveImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { - return nil, nil -} diff --git a/vendor/github.com/containers/image/v4/docker/archive/transport.go b/vendor/github.com/containers/image/v5/docker/archive/transport.go similarity index 97% rename from vendor/github.com/containers/image/v4/docker/archive/transport.go rename to vendor/github.com/containers/image/v5/docker/archive/transport.go index 347fdbd6ecc..44213bb8dc0 100644 --- a/vendor/github.com/containers/image/v4/docker/archive/transport.go +++ b/vendor/github.com/containers/image/v5/docker/archive/transport.go @@ -5,10 +5,10 @@ import ( "fmt" "strings" - "github.com/containers/image/v4/docker/reference" - ctrImage "github.com/containers/image/v4/image" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + ctrImage "github.com/containers/image/v5/image" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containers/image/v4/docker/cache.go b/vendor/github.com/containers/image/v5/docker/cache.go similarity index 89% rename from vendor/github.com/containers/image/v4/docker/cache.go rename to vendor/github.com/containers/image/v5/docker/cache.go index 51bf5b0d32a..728d32d170c 100644 --- a/vendor/github.com/containers/image/v4/docker/cache.go +++ b/vendor/github.com/containers/image/v5/docker/cache.go @@ -1,8 +1,8 @@ package docker import ( - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/types" ) // bicTransportScope returns a BICTransportScope appropriate for ref. diff --git a/vendor/github.com/containers/image/v4/docker/daemon/client.go b/vendor/github.com/containers/image/v5/docker/daemon/client.go similarity index 98% rename from vendor/github.com/containers/image/v4/docker/daemon/client.go rename to vendor/github.com/containers/image/v5/docker/daemon/client.go index 94c4970f2ee..323a02fc095 100644 --- a/vendor/github.com/containers/image/v4/docker/daemon/client.go +++ b/vendor/github.com/containers/image/v5/docker/daemon/client.go @@ -4,7 +4,7 @@ import ( "net/http" "path/filepath" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" dockerclient "github.com/docker/docker/client" "github.com/docker/go-connections/tlsconfig" ) diff --git a/vendor/github.com/containers/image/v4/docker/daemon/daemon_dest.go b/vendor/github.com/containers/image/v5/docker/daemon/daemon_dest.go similarity index 95% rename from vendor/github.com/containers/image/v4/docker/daemon/daemon_dest.go rename to vendor/github.com/containers/image/v5/docker/daemon/daemon_dest.go index 2c56ab9344a..25ce55a17c4 100644 --- a/vendor/github.com/containers/image/v4/docker/daemon/daemon_dest.go +++ b/vendor/github.com/containers/image/v5/docker/daemon/daemon_dest.go @@ -4,9 +4,9 @@ import ( "context" "io" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/docker/tarfile" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/docker/tarfile" + "github.com/containers/image/v5/types" "github.com/docker/docker/client" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -124,7 +124,7 @@ func (d *daemonImageDestination) Reference() types.ImageReference { // WARNING: This does not have any transactional semantics: // - Uploaded data MAY be visible to others before Commit() is called // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed) -func (d *daemonImageDestination) Commit(ctx context.Context) error { +func (d *daemonImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error { logrus.Debugf("docker-daemon: Closing tar stream") if err := d.Destination.Commit(ctx); err != nil { return err diff --git a/vendor/github.com/containers/image/v4/docker/daemon/daemon_src.go b/vendor/github.com/containers/image/v5/docker/daemon/daemon_src.go similarity index 85% rename from vendor/github.com/containers/image/v4/docker/daemon/daemon_src.go rename to vendor/github.com/containers/image/v5/docker/daemon/daemon_src.go index f6f60aaf913..46fbcc4e0bf 100644 --- a/vendor/github.com/containers/image/v4/docker/daemon/daemon_src.go +++ b/vendor/github.com/containers/image/v5/docker/daemon/daemon_src.go @@ -3,8 +3,8 @@ package daemon import ( "context" - "github.com/containers/image/v4/docker/tarfile" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/tarfile" + "github.com/containers/image/v5/types" "github.com/pkg/errors" ) @@ -55,8 +55,3 @@ func newImageSource(ctx context.Context, sys *types.SystemContext, ref daemonRef func (s *daemonImageSource) Reference() types.ImageReference { return s.ref } - -// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified. -func (s *daemonImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { - return nil, nil -} diff --git a/vendor/github.com/containers/image/v4/docker/daemon/daemon_transport.go b/vendor/github.com/containers/image/v5/docker/daemon/daemon_transport.go similarity index 97% rename from vendor/github.com/containers/image/v4/docker/daemon/daemon_transport.go rename to vendor/github.com/containers/image/v5/docker/daemon/daemon_transport.go index 4c6986ba016..4e4ed688148 100644 --- a/vendor/github.com/containers/image/v4/docker/daemon/daemon_transport.go +++ b/vendor/github.com/containers/image/v5/docker/daemon/daemon_transport.go @@ -4,11 +4,11 @@ import ( "context" "fmt" - "github.com/containers/image/v4/docker/policyconfiguration" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/image" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/policyconfiguration" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containers/image/v4/docker/docker_client.go b/vendor/github.com/containers/image/v5/docker/docker_client.go similarity index 85% rename from vendor/github.com/containers/image/v4/docker/docker_client.go rename to vendor/github.com/containers/image/v5/docker/docker_client.go index d5662a030f9..0b012c703b0 100644 --- a/vendor/github.com/containers/image/v4/docker/docker_client.go +++ b/vendor/github.com/containers/image/v5/docker/docker_client.go @@ -16,12 +16,12 @@ import ( "sync" "time" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/pkg/docker/config" - "github.com/containers/image/v4/pkg/sysregistriesv2" - "github.com/containers/image/v4/pkg/tlsclientconfig" - "github.com/containers/image/v4/types" - "github.com/docker/distribution/registry/client" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/pkg/docker/config" + "github.com/containers/image/v5/pkg/sysregistriesv2" + "github.com/containers/image/v5/pkg/tlsclientconfig" + "github.com/containers/image/v5/types" + clientLib "github.com/docker/distribution/registry/client" "github.com/docker/go-connections/tlsconfig" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -47,14 +47,7 @@ const ( extensionSignatureTypeAtomic = "atomic" // extensionSignature.Type ) -var ( - // ErrV1NotSupported is returned when we're trying to talk to a - // docker V1 registry. - ErrV1NotSupported = errors.New("can't talk to a V1 docker registry") - // ErrUnauthorizedForCredentials is returned when the status code returned is 401 - ErrUnauthorizedForCredentials = errors.New("unable to retrieve auth token: invalid username/password") - systemPerHostCertDirPaths = [2]string{"/etc/containers/certs.d", "/etc/docker/certs.d"} -) +var systemPerHostCertDirPaths = [2]string{"/etc/containers/certs.d", "/etc/docker/certs.d"} // extensionSignature and extensionSignatureList come from github.com/openshift/origin/pkg/dockerregistry/server/signaturedispatcher.go: // signature represents a Docker image signature. @@ -284,14 +277,7 @@ func CheckAuth(ctx context.Context, sys *types.SystemContext, username, password } defer resp.Body.Close() - switch resp.StatusCode { - case http.StatusOK: - return nil - case http.StatusUnauthorized: - return ErrUnauthorizedForCredentials - default: - return errors.Errorf("error occured with status code %d (%s)", resp.StatusCode, http.StatusText(resp.StatusCode)) - } + return httpResponseToError(resp) } // SearchResult holds the information of each matching image @@ -365,7 +351,7 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima } else { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - logrus.Debugf("error getting search results from v1 endpoint %q, status code %d (%s)", registry, resp.StatusCode, http.StatusText(resp.StatusCode)) + logrus.Debugf("error getting search results from v1 endpoint %q: %v", registry, httpResponseToError(resp)) } else { if err := json.NewDecoder(resp.Body).Decode(v1Res); err != nil { return nil, err @@ -382,7 +368,7 @@ func SearchRegistry(ctx context.Context, sys *types.SystemContext, registry, ima } else { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - logrus.Errorf("error getting search results from v2 endpoint %q, status code %d (%s)", registry, resp.StatusCode, http.StatusText(resp.StatusCode)) + logrus.Errorf("error getting search results from v2 endpoint %q: %v", registry, httpResponseToError(resp)) } else { if err := json.NewDecoder(resp.Body).Decode(v2Res); err != nil { return nil, err @@ -417,8 +403,78 @@ func (c *dockerClient) makeRequest(ctx context.Context, method, path string, hea // makeRequestToResolvedURL creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client. // streamLen, if not -1, specifies the length of the data expected on stream. // makeRequest should generally be preferred. +// In case of an http 429 status code in the response, it performs an exponential back off starting at 2 seconds for at most 5 iterations. +// If the `Retry-After` header is set in the response, the specified value or date is +// If the stream is non-nil, no back off will be performed. // TODO(runcom): too many arguments here, use a struct func (c *dockerClient) makeRequestToResolvedURL(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, auth sendAuth, extraScope *authScope) (*http.Response, error) { + var ( + res *http.Response + err error + delay int64 + ) + delay = 2 + const numIterations = 5 + const maxDelay = 60 + + // math.Min() only supports float64, so have an anonymous func to avoid + // casting. + min := func(a int64, b int64) int64 { + if a < b { + return a + } + return b + } + + nextDelay := func(r *http.Response, delay int64) int64 { + after := res.Header.Get("Retry-After") + if after == "" { + return min(delay, maxDelay) + } + logrus.Debugf("detected 'Retry-After' header %q", after) + // First check if we have a numerical value. + if num, err := strconv.ParseInt(after, 10, 64); err == nil { + return min(num, maxDelay) + } + // Secondly check if we have an http date. + // If the delta between the date and now is positive, use it. + // Otherwise, fall back to using the default exponential back off. + if t, err := http.ParseTime(after); err == nil { + delta := int64(t.Sub(time.Now()).Seconds()) + if delta > 0 { + return min(delta, maxDelay) + } + logrus.Debugf("negative date: falling back to using %d seconds", delay) + return min(delay, maxDelay) + } + // If the header contains bogus, fall back to using the default + // exponential back off. + logrus.Debugf("invalid format: falling back to using %d seconds", delay) + return min(delay, maxDelay) + } + + for i := 0; i < numIterations; i++ { + res, err = c.makeRequestToResolvedURLOnce(ctx, method, url, headers, stream, streamLen, auth, extraScope) + if stream == nil && res != nil && res.StatusCode == http.StatusTooManyRequests { + if i < numIterations-1 { + logrus.Errorf("HEADER %v", res.Header) + delay = nextDelay(res, delay) // compute next delay - does NOT exceed maxDelay + logrus.Debugf("too many request to %s: sleeping for %d seconds before next attempt", url, delay) + time.Sleep(time.Duration(delay) * time.Second) + delay = delay * 2 // exponential back off + } + continue + } + break + } + return res, err +} + +// makeRequestToResolvedURLOnce creates and executes a http.Request with the specified parameters, adding authentication and TLS options for the Docker client. +// streamLen, if not -1, specifies the length of the data expected on stream. +// makeRequest should generally be preferred. +// Note that no exponential back off is performed when receiving an http 429 status code. +func (c *dockerClient) makeRequestToResolvedURLOnce(ctx context.Context, method, url string, headers map[string][]string, stream io.Reader, streamLen int64, auth sendAuth, extraScope *authScope) (*http.Response, error) { req, err := http.NewRequest(method, url, stream) if err != nil { return nil, err @@ -533,9 +589,9 @@ func (c *dockerClient) getBearerToken(ctx context.Context, challenge challenge, defer res.Body.Close() switch res.StatusCode { case http.StatusUnauthorized: - err := client.HandleErrorResponse(res) + err := clientLib.HandleErrorResponse(res) logrus.Debugf("Server response when trying to obtain an access token: \n%q", err.Error()) - return nil, ErrUnauthorizedForCredentials + return nil, ErrUnauthorizedForCredentials{Err: err} case http.StatusOK: break default: @@ -571,7 +627,7 @@ func (c *dockerClient) detectPropertiesHelper(ctx context.Context) error { defer resp.Body.Close() logrus.Debugf("Ping %s status %d", url, resp.StatusCode) if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusUnauthorized { - return errors.Errorf("error pinging registry %s, response code %d (%s)", c.registry, resp.StatusCode, http.StatusText(resp.StatusCode)) + return httpResponseToError(resp) } c.challenges = parseAuthHeader(resp.Header) c.scheme = scheme @@ -583,7 +639,7 @@ func (c *dockerClient) detectPropertiesHelper(ctx context.Context) error { err = ping("http") } if err != nil { - err = errors.Wrap(err, "pinging docker registry returned") + err = errors.Wrapf(err, "error pinging docker registry %s", c.registry) if c.sys != nil && c.sys.DockerDisableV1Ping { return err } @@ -629,9 +685,11 @@ func (c *dockerClient) getExtensionsSignatures(ctx context.Context, ref dockerRe return nil, err } defer res.Body.Close() + if res.StatusCode != http.StatusOK { - return nil, errors.Wrapf(client.HandleErrorResponse(res), "Error downloading signatures for %s in %s", manifestDigest, ref.ref.Name()) + return nil, errors.Wrapf(clientLib.HandleErrorResponse(res), "Error downloading signatures for %s in %s", manifestDigest, ref.ref.Name()) } + body, err := ioutil.ReadAll(res.Body) if err != nil { return nil, err diff --git a/vendor/github.com/containers/image/v4/docker/docker_image.go b/vendor/github.com/containers/image/v5/docker/docker_image.go similarity index 88% rename from vendor/github.com/containers/image/v4/docker/docker_image.go rename to vendor/github.com/containers/image/v5/docker/docker_image.go index 4332dc020f3..dad382cd0d4 100644 --- a/vendor/github.com/containers/image/v4/docker/docker_image.go +++ b/vendor/github.com/containers/image/v5/docker/docker_image.go @@ -4,13 +4,12 @@ import ( "context" "encoding/json" "fmt" - "net/http" "net/url" "strings" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/image" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/types" "github.com/pkg/errors" ) @@ -71,9 +70,8 @@ func GetRepositoryTags(ctx context.Context, sys *types.SystemContext, ref types. return nil, err } defer res.Body.Close() - if res.StatusCode != http.StatusOK { - // print url also - return nil, errors.Errorf("Invalid status code returned when fetching tags list %d (%s)", res.StatusCode, http.StatusText(res.StatusCode)) + if err := httpResponseToError(res); err != nil { + return nil, err } var tagsHolder struct { diff --git a/vendor/github.com/containers/image/v4/docker/docker_image_dest.go b/vendor/github.com/containers/image/v5/docker/docker_image_dest.go similarity index 87% rename from vendor/github.com/containers/image/v4/docker/docker_image_dest.go rename to vendor/github.com/containers/image/v5/docker/docker_image_dest.go index 0f351ab5941..417d97aec93 100644 --- a/vendor/github.com/containers/image/v4/docker/docker_image_dest.go +++ b/vendor/github.com/containers/image/v5/docker/docker_image_dest.go @@ -14,12 +14,12 @@ import ( "path/filepath" "strings" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/pkg/blobinfocache/none" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/blobinfocache/none" + "github.com/containers/image/v5/types" "github.com/docker/distribution/registry/api/errcode" - "github.com/docker/distribution/registry/api/v2" + v2 "github.com/docker/distribution/registry/api/v2" "github.com/docker/distribution/registry/client" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -61,6 +61,8 @@ func (d *dockerImageDestination) SupportedManifestMIMETypes() []string { return []string{ imgspecv1.MediaTypeImageManifest, manifest.DockerV2Schema2MediaType, + imgspecv1.MediaTypeImageIndex, + manifest.DockerV2ListMediaType, manifest.DockerV2Schema1SignedMediaType, manifest.DockerV2Schema1MediaType, } @@ -343,20 +345,47 @@ func (d *dockerImageDestination) TryReusingBlob(ctx context.Context, info types. } // PutManifest writes manifest to the destination. +// When the primary manifest is a manifest list, if instanceDigest is nil, we're saving the list +// itself, else instanceDigest contains a digest of the specific manifest instance to overwrite the +// manifest for; when the primary manifest is not a manifest list, instanceDigest should always be nil. // FIXME? This should also receive a MIME type if known, to differentiate between schema versions. // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema), // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError. -func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte) error { - digest, err := manifest.Digest(m) - if err != nil { - return err +func (d *dockerImageDestination) PutManifest(ctx context.Context, m []byte, instanceDigest *digest.Digest) error { + refTail := "" + if instanceDigest != nil { + // If the instanceDigest is provided, then use it as the refTail, because the reference, + // whether it includes a tag or a digest, refers to the list as a whole, and not this + // particular instance. + refTail = instanceDigest.String() + // Double-check that the manifest we've been given matches the digest we've been given. + matches, err := manifest.MatchesDigest(m, *instanceDigest) + if err != nil { + return errors.Wrapf(err, "error digesting manifest in PutManifest") + } + if !matches { + manifestDigest, merr := manifest.Digest(m) + if merr != nil { + return errors.Wrapf(err, "Attempted to PutManifest using an explicitly specified digest (%q) that didn't match the manifest's digest (%v attempting to compute it)", instanceDigest.String(), merr) + } + return errors.Errorf("Attempted to PutManifest using an explicitly specified digest (%q) that didn't match the manifest's digest (%q)", instanceDigest.String(), manifestDigest.String()) + } + } else { + // Compute the digest of the main manifest, or the list if it's a list, so that we + // have a digest value to use if we're asked to save a signature for the manifest. + digest, err := manifest.Digest(m) + if err != nil { + return err + } + d.manifestDigest = digest + // The refTail should be either a digest (which we expect to match the value we just + // computed) or a tag name. + refTail, err = d.ref.tagOrDigest() + if err != nil { + return err + } } - d.manifestDigest = digest - refTail, err := d.ref.tagOrDigest() - if err != nil { - return err - } path := fmt.Sprintf(manifestPath, reference.Path(d.ref.ref), refTail) headers := map[string][]string{} @@ -416,19 +445,30 @@ func isManifestInvalidError(err error) bool { } } -func (d *dockerImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error { +// PutSignatures uploads a set of signatures to the relevant lookaside or API extension point. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to upload the signatures for (when +// the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list. +func (d *dockerImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error { // Do not fail if we don’t really need to support signatures. if len(signatures) == 0 { return nil } + if instanceDigest == nil { + if d.manifestDigest.String() == "" { + // This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures + return errors.Errorf("Unknown manifest digest, can't add signatures") + } + instanceDigest = &d.manifestDigest + } + if err := d.c.detectProperties(ctx); err != nil { return err } switch { case d.c.signatureBase != nil: - return d.putSignaturesToLookaside(signatures) + return d.putSignaturesToLookaside(signatures, instanceDigest) case d.c.supportsSignatures: - return d.putSignaturesToAPIExtension(ctx, signatures) + return d.putSignaturesToAPIExtension(ctx, signatures, instanceDigest) default: return errors.Errorf("X-Registry-Supports-Signatures extension not supported, and lookaside is not configured") } @@ -436,7 +476,7 @@ func (d *dockerImageDestination) PutSignatures(ctx context.Context, signatures [ // putSignaturesToLookaside implements PutSignatures() from the lookaside location configured in s.c.signatureBase, // which is not nil. -func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte) error { +func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte, instanceDigest *digest.Digest) error { // FIXME? This overwrites files one at a time, definitely not atomic. // A failure when updating signatures with a reordered copy could lose some of them. @@ -445,14 +485,9 @@ func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte) e return nil } - if d.manifestDigest.String() == "" { - // This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures - return errors.Errorf("Unknown manifest digest, can't add signatures") - } - // NOTE: Keep this in sync with docs/signature-protocols.md! for i, signature := range signatures { - url := signatureStorageURL(d.c.signatureBase, d.manifestDigest, i) + url := signatureStorageURL(d.c.signatureBase, *instanceDigest, i) if url == nil { return errors.Errorf("Internal error: signatureStorageURL with non-nil base returned nil") } @@ -467,7 +502,7 @@ func (d *dockerImageDestination) putSignaturesToLookaside(signatures [][]byte) e // is enough for dockerImageSource to stop looking for other signatures, so that // is sufficient. for i := len(signatures); ; i++ { - url := signatureStorageURL(d.c.signatureBase, d.manifestDigest, i) + url := signatureStorageURL(d.c.signatureBase, *instanceDigest, i) if url == nil { return errors.Errorf("Internal error: signatureStorageURL with non-nil base returned nil") } @@ -527,22 +562,17 @@ func (c *dockerClient) deleteOneSignature(url *url.URL) (missing bool, err error } // putSignaturesToAPIExtension implements PutSignatures() using the X-Registry-Supports-Signatures API extension. -func (d *dockerImageDestination) putSignaturesToAPIExtension(ctx context.Context, signatures [][]byte) error { +func (d *dockerImageDestination) putSignaturesToAPIExtension(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error { // Skip dealing with the manifest digest, or reading the old state, if not necessary. if len(signatures) == 0 { return nil } - if d.manifestDigest.String() == "" { - // This shouldn’t happen, ImageDestination users are required to call PutManifest before PutSignatures - return errors.Errorf("Unknown manifest digest, can't add signatures") - } - // Because image signatures are a shared resource in Atomic Registry, the default upload // always adds signatures. Eventually we should also allow removing signatures, // but the X-Registry-Supports-Signatures API extension does not support that yet. - existingSignatures, err := d.c.getExtensionsSignatures(ctx, d.ref, d.manifestDigest) + existingSignatures, err := d.c.getExtensionsSignatures(ctx, d.ref, *instanceDigest) if err != nil { return err } @@ -567,7 +597,7 @@ sigExists: if err != nil || n != 16 { return errors.Wrapf(err, "Error generating random signature len %d", n) } - signatureName = fmt.Sprintf("%s@%032x", d.manifestDigest.String(), randBytes) + signatureName = fmt.Sprintf("%s@%032x", instanceDigest.String(), randBytes) if _, ok := existingSigNames[signatureName]; !ok { break } @@ -606,6 +636,6 @@ sigExists: // WARNING: This does not have any transactional semantics: // - Uploaded data MAY be visible to others before Commit() is called // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed) -func (d *dockerImageDestination) Commit(ctx context.Context) error { +func (d *dockerImageDestination) Commit(context.Context, types.UnparsedImage) error { return nil } diff --git a/vendor/github.com/containers/image/v4/docker/docker_image_src.go b/vendor/github.com/containers/image/v5/docker/docker_image_src.go similarity index 93% rename from vendor/github.com/containers/image/v4/docker/docker_image_src.go rename to vendor/github.com/containers/image/v5/docker/docker_image_src.go index 353b1a6c597..35beb30e54f 100644 --- a/vendor/github.com/containers/image/v4/docker/docker_image_src.go +++ b/vendor/github.com/containers/image/v5/docker/docker_image_src.go @@ -11,10 +11,10 @@ import ( "os" "strconv" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/pkg/sysregistriesv2" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/sysregistriesv2" + "github.com/containers/image/v5/types" "github.com/docker/distribution/registry/client" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" @@ -103,8 +103,15 @@ func (s *dockerImageSource) Close() error { return nil } -// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified. -func (s *dockerImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { +// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer +// blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob() +// to read the image's layers. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for +// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list +// (e.g. if the source never returns manifest lists). +// The Digest field is guaranteed to be provided; Size may be -1. +// WARNING: The list may contain duplicates, and they are semantically relevant. +func (s *dockerImageSource) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) { return nil, nil } @@ -232,9 +239,8 @@ func (s *dockerImageSource) GetBlob(ctx context.Context, info types.BlobInfo, ca if err != nil { return nil, 0, err } - if res.StatusCode != http.StatusOK { - // print url also - return nil, 0, errors.Errorf("Invalid status code returned when fetching blob %d (%s)", res.StatusCode, http.StatusText(res.StatusCode)) + if err := httpResponseToError(res); err != nil { + return nil, 0, err } cache.RecordKnownLocation(s.ref.Transport(), bicTransportScope(s.ref), info.Digest, newBICLocationReference(s.ref)) return res.Body, getBlobSize(res), nil diff --git a/vendor/github.com/containers/image/v4/docker/docker_transport.go b/vendor/github.com/containers/image/v5/docker/docker_transport.go similarity index 97% rename from vendor/github.com/containers/image/v4/docker/docker_transport.go rename to vendor/github.com/containers/image/v5/docker/docker_transport.go index c9ce75e0d85..8b8e5796833 100644 --- a/vendor/github.com/containers/image/v4/docker/docker_transport.go +++ b/vendor/github.com/containers/image/v5/docker/docker_transport.go @@ -5,10 +5,10 @@ import ( "fmt" "strings" - "github.com/containers/image/v4/docker/policyconfiguration" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/policyconfiguration" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containers/image/v5/docker/errors.go b/vendor/github.com/containers/image/v5/docker/errors.go new file mode 100644 index 00000000000..860868f415a --- /dev/null +++ b/vendor/github.com/containers/image/v5/docker/errors.go @@ -0,0 +1,43 @@ +package docker + +import ( + "errors" + "fmt" + "net/http" + + "github.com/docker/distribution/registry/client" + perrors "github.com/pkg/errors" +) + +var ( + // ErrV1NotSupported is returned when we're trying to talk to a + // docker V1 registry. + ErrV1NotSupported = errors.New("can't talk to a V1 docker registry") + // ErrTooManyRequests is returned when the status code returned is 429 + ErrTooManyRequests = errors.New("too many request to registry") +) + +// ErrUnauthorizedForCredentials is returned when the status code returned is 401 +type ErrUnauthorizedForCredentials struct { // We only use a struct to allow a type assertion, without limiting the contents of the error otherwise. + Err error +} + +func (e ErrUnauthorizedForCredentials) Error() string { + return fmt.Sprintf("unable to retrieve auth token: invalid username/password: %s", e.Err.Error()) +} + +// httpResponseToError translates the https.Response into an error. It returns +// nil if the response is not considered an error. +func httpResponseToError(res *http.Response) error { + switch res.StatusCode { + case http.StatusOK: + return nil + case http.StatusTooManyRequests: + return ErrTooManyRequests + case http.StatusUnauthorized: + err := client.HandleErrorResponse(res) + return ErrUnauthorizedForCredentials{Err: err} + default: + return perrors.Errorf("invalid status code from registry %d (%s)", res.StatusCode, http.StatusText(res.StatusCode)) + } +} diff --git a/vendor/github.com/containers/image/v4/docker/lookaside.go b/vendor/github.com/containers/image/v5/docker/lookaside.go similarity index 98% rename from vendor/github.com/containers/image/v4/docker/lookaside.go rename to vendor/github.com/containers/image/v5/docker/lookaside.go index c43160f7256..918c0f8389d 100644 --- a/vendor/github.com/containers/image/v4/docker/lookaside.go +++ b/vendor/github.com/containers/image/v5/docker/lookaside.go @@ -9,8 +9,8 @@ import ( "path/filepath" "strings" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/types" "github.com/ghodss/yaml" "github.com/opencontainers/go-digest" "github.com/pkg/errors" diff --git a/vendor/github.com/containers/image/v4/docker/policyconfiguration/naming.go b/vendor/github.com/containers/image/v5/docker/policyconfiguration/naming.go similarity index 97% rename from vendor/github.com/containers/image/v4/docker/policyconfiguration/naming.go rename to vendor/github.com/containers/image/v5/docker/policyconfiguration/naming.go index e2ed631c508..61d9aab9a98 100644 --- a/vendor/github.com/containers/image/v4/docker/policyconfiguration/naming.go +++ b/vendor/github.com/containers/image/v5/docker/policyconfiguration/naming.go @@ -3,7 +3,7 @@ package policyconfiguration import ( "strings" - "github.com/containers/image/v4/docker/reference" + "github.com/containers/image/v5/docker/reference" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containers/image/v4/docker/reference/README.md b/vendor/github.com/containers/image/v5/docker/reference/README.md similarity index 100% rename from vendor/github.com/containers/image/v4/docker/reference/README.md rename to vendor/github.com/containers/image/v5/docker/reference/README.md diff --git a/vendor/github.com/containers/image/v4/docker/reference/helpers.go b/vendor/github.com/containers/image/v5/docker/reference/helpers.go similarity index 100% rename from vendor/github.com/containers/image/v4/docker/reference/helpers.go rename to vendor/github.com/containers/image/v5/docker/reference/helpers.go diff --git a/vendor/github.com/containers/image/v4/docker/reference/normalize.go b/vendor/github.com/containers/image/v5/docker/reference/normalize.go similarity index 100% rename from vendor/github.com/containers/image/v4/docker/reference/normalize.go rename to vendor/github.com/containers/image/v5/docker/reference/normalize.go diff --git a/vendor/github.com/containers/image/v4/docker/reference/reference.go b/vendor/github.com/containers/image/v5/docker/reference/reference.go similarity index 100% rename from vendor/github.com/containers/image/v4/docker/reference/reference.go rename to vendor/github.com/containers/image/v5/docker/reference/reference.go diff --git a/vendor/github.com/containers/image/v4/docker/reference/regexp.go b/vendor/github.com/containers/image/v5/docker/reference/regexp.go similarity index 100% rename from vendor/github.com/containers/image/v4/docker/reference/regexp.go rename to vendor/github.com/containers/image/v5/docker/reference/regexp.go diff --git a/vendor/github.com/containers/image/v4/docker/tarfile/dest.go b/vendor/github.com/containers/image/v5/docker/tarfile/dest.go similarity index 93% rename from vendor/github.com/containers/image/v4/docker/tarfile/dest.go rename to vendor/github.com/containers/image/v5/docker/tarfile/dest.go index aec8404b67b..b02c60bb3d3 100644 --- a/vendor/github.com/containers/image/v4/docker/tarfile/dest.go +++ b/vendor/github.com/containers/image/v5/docker/tarfile/dest.go @@ -12,10 +12,10 @@ import ( "path/filepath" "time" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/internal/tmpdir" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/internal/tmpdir" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -195,10 +195,15 @@ func (d *Destination) createRepositoriesFile(rootLayerID string) error { } // PutManifest writes manifest to the destination. +// The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so +// there can be no secondary manifests. // FIXME? This should also receive a MIME type if known, to differentiate between schema versions. // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema), // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError. -func (d *Destination) PutManifest(ctx context.Context, m []byte) error { +func (d *Destination) PutManifest(ctx context.Context, m []byte, instanceDigest *digest.Digest) error { + if instanceDigest != nil { + return errors.New(`Manifest lists are not supported for docker tar files`) + } // We do not bother with types.ManifestTypeRejectedError; our .SupportedManifestMIMETypes() above is already providing only one alternative, // so the caller trying a different manifest kind would be pointless. var man manifest.Schema2 @@ -390,10 +395,13 @@ func (d *Destination) sendFile(path string, expectedSize int64, stream io.Reader return nil } -// PutSignatures adds the given signatures to the docker tarfile (currently not -// supported). MUST be called after PutManifest (signatures reference manifest -// contents) -func (d *Destination) PutSignatures(ctx context.Context, signatures [][]byte) error { +// PutSignatures would add the given signatures to the docker tarfile (currently not supported). +// The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so +// there can be no secondary manifests. MUST be called after PutManifest (signatures reference manifest contents). +func (d *Destination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error { + if instanceDigest != nil { + return errors.Errorf(`Manifest lists are not supported for docker tar files`) + } if len(signatures) != 0 { return errors.Errorf("Storing signatures for docker tar files is not supported") } diff --git a/vendor/github.com/containers/image/v4/docker/tarfile/doc.go b/vendor/github.com/containers/image/v5/docker/tarfile/doc.go similarity index 100% rename from vendor/github.com/containers/image/v4/docker/tarfile/doc.go rename to vendor/github.com/containers/image/v5/docker/tarfile/doc.go diff --git a/vendor/github.com/containers/image/v4/docker/tarfile/src.go b/vendor/github.com/containers/image/v5/docker/tarfile/src.go similarity index 92% rename from vendor/github.com/containers/image/v4/docker/tarfile/src.go rename to vendor/github.com/containers/image/v5/docker/tarfile/src.go index 78e4d6f656a..ad0a3d2cb4f 100644 --- a/vendor/github.com/containers/image/v4/docker/tarfile/src.go +++ b/vendor/github.com/containers/image/v5/docker/tarfile/src.go @@ -11,10 +11,10 @@ import ( "path" "sync" - "github.com/containers/image/v4/internal/tmpdir" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/pkg/compression" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/internal/tmpdir" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/compression" + "github.com/containers/image/v5/types" digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -349,10 +349,12 @@ func (s *Source) prepareLayerData(tarManifest *ManifestItem, parsedConfig *manif // It may use a remote (= slow) service. // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve (when the primary manifest is a manifest list); // this never happens if the primary manifest is not a manifest list (e.g. if the source never returns manifest lists). +// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil, +// as the primary manifest can not be a list, so there can be no secondary instances. func (s *Source) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { if instanceDigest != nil { // How did we even get here? GetManifest(ctx, nil) has returned a manifest.DockerV2Schema2MediaType. - return nil, "", errors.Errorf(`Manifest lists are not supported by "docker-daemon:"`) + return nil, "", errors.New(`Manifest lists are not supported by "docker-daemon:"`) } if s.generatedManifest == nil { if err := s.ensureCachedDataIsPresent(); err != nil { @@ -466,9 +468,8 @@ func (s *Source) GetBlob(ctx context.Context, info types.BlobInfo, cache types.B } // GetSignatures returns the image's signatures. It may use a remote (= slow) service. -// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for -// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list -// (e.g. if the source never returns manifest lists). +// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil, +// as there can be no secondary manifests. func (s *Source) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { if instanceDigest != nil { // How did we even get here? GetManifest(ctx, nil) has returned a manifest.DockerV2Schema2MediaType. @@ -476,3 +477,14 @@ func (s *Source) GetSignatures(ctx context.Context, instanceDigest *digest.Diges } return [][]byte{}, nil } + +// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer +// blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob() +// to read the image's layers. +// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil, +// as the primary manifest can not be a list, so there can be no secondary manifests. +// The Digest field is guaranteed to be provided; Size may be -1. +// WARNING: The list may contain duplicates, and they are semantically relevant. +func (s *Source) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) { + return nil, nil +} diff --git a/vendor/github.com/containers/image/v4/docker/tarfile/types.go b/vendor/github.com/containers/image/v5/docker/tarfile/types.go similarity index 94% rename from vendor/github.com/containers/image/v4/docker/tarfile/types.go rename to vendor/github.com/containers/image/v5/docker/tarfile/types.go index c630f52274b..ac222528a08 100644 --- a/vendor/github.com/containers/image/v4/docker/tarfile/types.go +++ b/vendor/github.com/containers/image/v5/docker/tarfile/types.go @@ -1,7 +1,7 @@ package tarfile import ( - "github.com/containers/image/v4/manifest" + "github.com/containers/image/v5/manifest" "github.com/opencontainers/go-digest" ) diff --git a/vendor/github.com/containers/image/v4/docker/wwwauthenticate.go b/vendor/github.com/containers/image/v5/docker/wwwauthenticate.go similarity index 100% rename from vendor/github.com/containers/image/v4/docker/wwwauthenticate.go rename to vendor/github.com/containers/image/v5/docker/wwwauthenticate.go diff --git a/vendor/github.com/containers/image/v5/image/docker_list.go b/vendor/github.com/containers/image/v5/image/docker_list.go new file mode 100644 index 00000000000..651c301aa33 --- /dev/null +++ b/vendor/github.com/containers/image/v5/image/docker_list.go @@ -0,0 +1,34 @@ +package image + +import ( + "context" + + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" + "github.com/pkg/errors" +) + +func manifestSchema2FromManifestList(ctx context.Context, sys *types.SystemContext, src types.ImageSource, manblob []byte) (genericManifest, error) { + list, err := manifest.Schema2ListFromManifest(manblob) + if err != nil { + return nil, errors.Wrapf(err, "Error parsing schema2 manifest list") + } + targetManifestDigest, err := list.ChooseInstance(sys) + if err != nil { + return nil, errors.Wrapf(err, "Error choosing image instance") + } + manblob, mt, err := src.GetManifest(ctx, &targetManifestDigest) + if err != nil { + return nil, errors.Wrapf(err, "Error loading manifest for target platform") + } + + matches, err := manifest.MatchesDigest(manblob, targetManifestDigest) + if err != nil { + return nil, errors.Wrap(err, "Error computing manifest digest") + } + if !matches { + return nil, errors.Errorf("Image manifest does not match selected manifest digest %s", targetManifestDigest) + } + + return manifestInstanceFromBlob(ctx, sys, src, manblob, mt) +} diff --git a/vendor/github.com/containers/image/v4/image/docker_schema1.go b/vendor/github.com/containers/image/v5/image/docker_schema1.go similarity index 98% rename from vendor/github.com/containers/image/v4/image/docker_schema1.go rename to vendor/github.com/containers/image/v5/image/docker_schema1.go index 97ebeac06bf..1a1c39d55b4 100644 --- a/vendor/github.com/containers/image/v4/image/docker_schema1.go +++ b/vendor/github.com/containers/image/v5/image/docker_schema1.go @@ -3,9 +3,9 @@ package image import ( "context" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" diff --git a/vendor/github.com/containers/image/v4/image/docker_schema2.go b/vendor/github.com/containers/image/v5/image/docker_schema2.go similarity index 98% rename from vendor/github.com/containers/image/v4/image/docker_schema2.go rename to vendor/github.com/containers/image/v5/image/docker_schema2.go index 9841bbd4270..254c13f7890 100644 --- a/vendor/github.com/containers/image/v4/image/docker_schema2.go +++ b/vendor/github.com/containers/image/v5/image/docker_schema2.go @@ -10,10 +10,10 @@ import ( "io/ioutil" "strings" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/pkg/blobinfocache/none" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/blobinfocache/none" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" diff --git a/vendor/github.com/containers/image/v4/image/manifest.go b/vendor/github.com/containers/image/v5/image/manifest.go similarity index 94% rename from vendor/github.com/containers/image/v4/image/manifest.go rename to vendor/github.com/containers/image/v5/image/manifest.go index f384d2fb8a1..fe66da15767 100644 --- a/vendor/github.com/containers/image/v4/image/manifest.go +++ b/vendor/github.com/containers/image/v5/image/manifest.go @@ -4,9 +4,9 @@ import ( "context" "fmt" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -58,6 +58,8 @@ func manifestInstanceFromBlob(ctx context.Context, sys *types.SystemContext, src return manifestSchema2FromManifest(src, manblob) case manifest.DockerV2ListMediaType: return manifestSchema2FromManifestList(ctx, sys, src, manblob) + case imgspecv1.MediaTypeImageIndex: + return manifestOCI1FromImageIndex(ctx, sys, src, manblob) default: // Note that this may not be reachable, manifest.NormalizedMIMEType has a default for unknown values. return nil, fmt.Errorf("Unimplemented manifest MIME type %s", mt) } diff --git a/vendor/github.com/containers/image/v4/image/memory.go b/vendor/github.com/containers/image/v5/image/memory.go similarity index 98% rename from vendor/github.com/containers/image/v4/image/memory.go rename to vendor/github.com/containers/image/v5/image/memory.go index 255965e145f..4c96b37d889 100644 --- a/vendor/github.com/containers/image/v4/image/memory.go +++ b/vendor/github.com/containers/image/v5/image/memory.go @@ -3,9 +3,8 @@ package image import ( "context" + "github.com/containers/image/v5/types" "github.com/pkg/errors" - - "github.com/containers/image/v4/types" ) // memoryImage is a mostly-implementation of types.Image assembled from data diff --git a/vendor/github.com/containers/image/v4/image/oci.go b/vendor/github.com/containers/image/v5/image/oci.go similarity index 97% rename from vendor/github.com/containers/image/v4/image/oci.go rename to vendor/github.com/containers/image/v5/image/oci.go index 142b0f28f3e..18a38d463ea 100644 --- a/vendor/github.com/containers/image/v4/image/oci.go +++ b/vendor/github.com/containers/image/v5/image/oci.go @@ -6,10 +6,10 @@ import ( "fmt" "io/ioutil" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/pkg/blobinfocache/none" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/blobinfocache/none" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" diff --git a/vendor/github.com/containers/image/v5/image/oci_index.go b/vendor/github.com/containers/image/v5/image/oci_index.go new file mode 100644 index 00000000000..022e03aca4d --- /dev/null +++ b/vendor/github.com/containers/image/v5/image/oci_index.go @@ -0,0 +1,34 @@ +package image + +import ( + "context" + + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" + "github.com/pkg/errors" +) + +func manifestOCI1FromImageIndex(ctx context.Context, sys *types.SystemContext, src types.ImageSource, manblob []byte) (genericManifest, error) { + index, err := manifest.OCI1IndexFromManifest(manblob) + if err != nil { + return nil, errors.Wrapf(err, "Error parsing OCI1 index") + } + targetManifestDigest, err := index.ChooseInstance(sys) + if err != nil { + return nil, errors.Wrapf(err, "Error choosing image instance") + } + manblob, mt, err := src.GetManifest(ctx, &targetManifestDigest) + if err != nil { + return nil, errors.Wrapf(err, "Error loading manifest for target platform") + } + + matches, err := manifest.MatchesDigest(manblob, targetManifestDigest) + if err != nil { + return nil, errors.Wrap(err, "Error computing manifest digest") + } + if !matches { + return nil, errors.Errorf("Image manifest does not match selected manifest digest %s", targetManifestDigest) + } + + return manifestInstanceFromBlob(ctx, sys, src, manblob, mt) +} diff --git a/vendor/github.com/containers/image/v4/image/sourced.go b/vendor/github.com/containers/image/v5/image/sourced.go similarity index 97% rename from vendor/github.com/containers/image/v4/image/sourced.go rename to vendor/github.com/containers/image/v5/image/sourced.go index d2a3e2ee627..3a016e1d09a 100644 --- a/vendor/github.com/containers/image/v4/image/sourced.go +++ b/vendor/github.com/containers/image/v5/image/sourced.go @@ -6,7 +6,7 @@ package image import ( "context" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" ) // imageCloser implements types.ImageCloser, perhaps allowing simple users @@ -100,5 +100,5 @@ func (i *sourcedImage) Manifest(ctx context.Context) ([]byte, string, error) { } func (i *sourcedImage) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { - return i.UnparsedImage.src.LayerInfosForCopy(ctx) + return i.UnparsedImage.src.LayerInfosForCopy(ctx, i.UnparsedImage.instanceDigest) } diff --git a/vendor/github.com/containers/image/v4/image/unparsed.go b/vendor/github.com/containers/image/v5/image/unparsed.go similarity index 96% rename from vendor/github.com/containers/image/v4/image/unparsed.go rename to vendor/github.com/containers/image/v5/image/unparsed.go index d731076547f..4e3028d855e 100644 --- a/vendor/github.com/containers/image/v4/image/unparsed.go +++ b/vendor/github.com/containers/image/v5/image/unparsed.go @@ -3,9 +3,9 @@ package image import ( "context" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containers/image/v4/internal/pkg/keyctl/key.go b/vendor/github.com/containers/image/v5/internal/pkg/keyctl/key.go similarity index 100% rename from vendor/github.com/containers/image/v4/internal/pkg/keyctl/key.go rename to vendor/github.com/containers/image/v5/internal/pkg/keyctl/key.go diff --git a/vendor/github.com/containers/image/v4/internal/pkg/keyctl/keyring.go b/vendor/github.com/containers/image/v5/internal/pkg/keyctl/keyring.go similarity index 100% rename from vendor/github.com/containers/image/v4/internal/pkg/keyctl/keyring.go rename to vendor/github.com/containers/image/v5/internal/pkg/keyctl/keyring.go diff --git a/vendor/github.com/containers/image/v4/internal/pkg/keyctl/perm.go b/vendor/github.com/containers/image/v5/internal/pkg/keyctl/perm.go similarity index 100% rename from vendor/github.com/containers/image/v4/internal/pkg/keyctl/perm.go rename to vendor/github.com/containers/image/v5/internal/pkg/keyctl/perm.go diff --git a/vendor/github.com/containers/image/v4/internal/pkg/keyctl/sys_linux.go b/vendor/github.com/containers/image/v5/internal/pkg/keyctl/sys_linux.go similarity index 100% rename from vendor/github.com/containers/image/v4/internal/pkg/keyctl/sys_linux.go rename to vendor/github.com/containers/image/v5/internal/pkg/keyctl/sys_linux.go diff --git a/vendor/github.com/containers/image/v4/internal/tmpdir/tmpdir.go b/vendor/github.com/containers/image/v5/internal/tmpdir/tmpdir.go similarity index 100% rename from vendor/github.com/containers/image/v4/internal/tmpdir/tmpdir.go rename to vendor/github.com/containers/image/v5/internal/tmpdir/tmpdir.go diff --git a/vendor/github.com/containers/image/v4/manifest/docker_schema1.go b/vendor/github.com/containers/image/v5/manifest/docker_schema1.go similarity index 99% rename from vendor/github.com/containers/image/v4/manifest/docker_schema1.go rename to vendor/github.com/containers/image/v5/manifest/docker_schema1.go index 3c172504abd..58527d7137b 100644 --- a/vendor/github.com/containers/image/v4/manifest/docker_schema1.go +++ b/vendor/github.com/containers/image/v5/manifest/docker_schema1.go @@ -6,8 +6,8 @@ import ( "strings" "time" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/types" "github.com/docker/docker/api/types/versions" "github.com/opencontainers/go-digest" "github.com/pkg/errors" diff --git a/vendor/github.com/containers/image/v4/manifest/docker_schema2.go b/vendor/github.com/containers/image/v5/manifest/docker_schema2.go similarity index 99% rename from vendor/github.com/containers/image/v4/manifest/docker_schema2.go rename to vendor/github.com/containers/image/v5/manifest/docker_schema2.go index 84b189c8e1e..d768d6e1143 100644 --- a/vendor/github.com/containers/image/v4/manifest/docker_schema2.go +++ b/vendor/github.com/containers/image/v5/manifest/docker_schema2.go @@ -5,9 +5,9 @@ import ( "fmt" "time" - "github.com/containers/image/v4/pkg/compression" - "github.com/containers/image/v4/pkg/strslice" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/pkg/compression" + "github.com/containers/image/v5/pkg/strslice" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" diff --git a/vendor/github.com/containers/image/v5/manifest/docker_schema2_list.go b/vendor/github.com/containers/image/v5/manifest/docker_schema2_list.go new file mode 100644 index 00000000000..453976c4873 --- /dev/null +++ b/vendor/github.com/containers/image/v5/manifest/docker_schema2_list.go @@ -0,0 +1,216 @@ +package manifest + +import ( + "encoding/json" + "fmt" + "runtime" + + "github.com/containers/image/v5/types" + "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +// Schema2PlatformSpec describes the platform which a particular manifest is +// specialized for. +type Schema2PlatformSpec struct { + Architecture string `json:"architecture"` + OS string `json:"os"` + OSVersion string `json:"os.version,omitempty"` + OSFeatures []string `json:"os.features,omitempty"` + Variant string `json:"variant,omitempty"` + Features []string `json:"features,omitempty"` // removed in OCI +} + +// Schema2ManifestDescriptor references a platform-specific manifest. +type Schema2ManifestDescriptor struct { + Schema2Descriptor + Platform Schema2PlatformSpec `json:"platform"` +} + +// Schema2List is a list of platform-specific manifests. +type Schema2List struct { + SchemaVersion int `json:"schemaVersion"` + MediaType string `json:"mediaType"` + Manifests []Schema2ManifestDescriptor `json:"manifests"` +} + +// MIMEType returns the MIME type of this particular manifest list. +func (list *Schema2List) MIMEType() string { + return list.MediaType +} + +// Instances returns a slice of digests of the manifests that this list knows of. +func (list *Schema2List) Instances() []digest.Digest { + results := make([]digest.Digest, len(list.Manifests)) + for i, m := range list.Manifests { + results[i] = m.Digest + } + return results +} + +// Instance returns the ListUpdate of a particular instance in the list. +func (list *Schema2List) Instance(instanceDigest digest.Digest) (ListUpdate, error) { + for _, manifest := range list.Manifests { + if manifest.Digest == instanceDigest { + return ListUpdate{ + Digest: manifest.Digest, + Size: manifest.Size, + MediaType: manifest.MediaType, + }, nil + } + } + return ListUpdate{}, errors.Errorf("unable to find instance %s passed to Schema2List.Instances", instanceDigest) +} + +// UpdateInstances updates the sizes, digests, and media types of the manifests +// which the list catalogs. +func (list *Schema2List) UpdateInstances(updates []ListUpdate) error { + if len(updates) != len(list.Manifests) { + return errors.Errorf("incorrect number of update entries passed to Schema2List.UpdateInstances: expected %d, got %d", len(list.Manifests), len(updates)) + } + for i := range updates { + if err := updates[i].Digest.Validate(); err != nil { + return errors.Wrapf(err, "update %d of %d passed to Schema2List.UpdateInstances contained an invalid digest", i+1, len(updates)) + } + list.Manifests[i].Digest = updates[i].Digest + if updates[i].Size < 0 { + return errors.Errorf("update %d of %d passed to Schema2List.UpdateInstances had an invalid size (%d)", i+1, len(updates), updates[i].Size) + } + list.Manifests[i].Size = updates[i].Size + if updates[i].MediaType == "" { + return errors.Errorf("update %d of %d passed to Schema2List.UpdateInstances had no media type (was %q)", i+1, len(updates), list.Manifests[i].MediaType) + } + if err := SupportedSchema2MediaType(updates[i].MediaType); err != nil && SupportedOCI1MediaType(updates[i].MediaType) != nil { + return errors.Wrapf(err, "update %d of %d passed to Schema2List.UpdateInstances had an unsupported media type (was %q): %q", i+1, len(updates), list.Manifests[i].MediaType, updates[i].MediaType) + } + list.Manifests[i].MediaType = updates[i].MediaType + } + return nil +} + +// ChooseInstance parses blob as a schema2 manifest list, and returns the digest +// of the image which is appropriate for the current environment. +func (list *Schema2List) ChooseInstance(ctx *types.SystemContext) (digest.Digest, error) { + wantedArch := runtime.GOARCH + if ctx != nil && ctx.ArchitectureChoice != "" { + wantedArch = ctx.ArchitectureChoice + } + wantedOS := runtime.GOOS + if ctx != nil && ctx.OSChoice != "" { + wantedOS = ctx.OSChoice + } + + for _, d := range list.Manifests { + if d.Platform.Architecture == wantedArch && d.Platform.OS == wantedOS { + return d.Digest, nil + } + } + return "", fmt.Errorf("no image found in manifest list for architecture %s, OS %s", wantedArch, wantedOS) +} + +// Serialize returns the list in a blob format. +// NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made! +func (list *Schema2List) Serialize() ([]byte, error) { + buf, err := json.Marshal(list) + if err != nil { + return nil, errors.Wrapf(err, "error marshaling Schema2List %#v", list) + } + return buf, nil +} + +// Schema2ListFromComponents creates a Schema2 manifest list instance from the +// supplied data. +func Schema2ListFromComponents(components []Schema2ManifestDescriptor) *Schema2List { + list := Schema2List{ + SchemaVersion: 2, + MediaType: DockerV2ListMediaType, + Manifests: make([]Schema2ManifestDescriptor, len(components)), + } + for i, component := range components { + m := Schema2ManifestDescriptor{ + Schema2Descriptor{ + MediaType: component.MediaType, + Size: component.Size, + Digest: component.Digest, + URLs: dupStringSlice(component.URLs), + }, + Schema2PlatformSpec{ + Architecture: component.Platform.Architecture, + OS: component.Platform.OS, + OSVersion: component.Platform.OSVersion, + OSFeatures: dupStringSlice(component.Platform.OSFeatures), + Variant: component.Platform.Variant, + Features: dupStringSlice(component.Platform.Features), + }, + } + list.Manifests[i] = m + } + return &list +} + +// Schema2ListClone creates a deep copy of the passed-in list. +func Schema2ListClone(list *Schema2List) *Schema2List { + return Schema2ListFromComponents(list.Manifests) +} + +// ToOCI1Index returns the list encoded as an OCI1 index. +func (list *Schema2List) ToOCI1Index() (*OCI1Index, error) { + components := make([]imgspecv1.Descriptor, 0, len(list.Manifests)) + for _, manifest := range list.Manifests { + converted := imgspecv1.Descriptor{ + MediaType: manifest.MediaType, + Size: manifest.Size, + Digest: manifest.Digest, + URLs: dupStringSlice(manifest.URLs), + Platform: &imgspecv1.Platform{ + OS: manifest.Platform.OS, + Architecture: manifest.Platform.Architecture, + OSFeatures: dupStringSlice(manifest.Platform.OSFeatures), + OSVersion: manifest.Platform.OSVersion, + Variant: manifest.Platform.Variant, + }, + } + components = append(components, converted) + } + oci := OCI1IndexFromComponents(components, nil) + return oci, nil +} + +// ToSchema2List returns the list encoded as a Schema2 list. +func (list *Schema2List) ToSchema2List() (*Schema2List, error) { + return Schema2ListClone(list), nil +} + +// Schema2ListFromManifest creates a Schema2 manifest list instance from marshalled +// JSON, presumably generated by encoding a Schema2 manifest list. +func Schema2ListFromManifest(manifest []byte) (*Schema2List, error) { + list := Schema2List{ + Manifests: []Schema2ManifestDescriptor{}, + } + if err := json.Unmarshal(manifest, &list); err != nil { + return nil, errors.Wrapf(err, "error unmarshaling Schema2List %q", string(manifest)) + } + return &list, nil +} + +// Clone returns a deep copy of this list and its contents. +func (list *Schema2List) Clone() List { + return Schema2ListClone(list) +} + +// ConvertToMIMEType converts the passed-in manifest list to a manifest +// list of the specified type. +func (list *Schema2List) ConvertToMIMEType(manifestMIMEType string) (List, error) { + switch normalized := NormalizedMIMEType(manifestMIMEType); normalized { + case DockerV2ListMediaType: + return list.Clone(), nil + case imgspecv1.MediaTypeImageIndex: + return list.ToOCI1Index() + case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType, imgspecv1.MediaTypeImageManifest, DockerV2Schema2MediaType: + return nil, fmt.Errorf("Can not convert manifest list to MIME type %q, which is not a list type", manifestMIMEType) + default: + // Note that this may not be reachable, NormalizedMIMEType has a default for unknown values. + return nil, fmt.Errorf("Unimplemented manifest list MIME type %s", manifestMIMEType) + } +} diff --git a/vendor/github.com/containers/image/v5/manifest/list.go b/vendor/github.com/containers/image/v5/manifest/list.go new file mode 100644 index 00000000000..6d10430fd7f --- /dev/null +++ b/vendor/github.com/containers/image/v5/manifest/list.go @@ -0,0 +1,106 @@ +package manifest + +import ( + "fmt" + + "github.com/containers/image/v5/types" + digest "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" +) + +var ( + // SupportedListMIMETypes is a list of the manifest list types that we know how to + // read/manipulate/write. + SupportedListMIMETypes = []string{ + DockerV2ListMediaType, + imgspecv1.MediaTypeImageIndex, + } +) + +// List is an interface for parsing, modifying lists of image manifests. +// Callers can either use this abstract interface without understanding the details of the formats, +// or instantiate a specific implementation (e.g. manifest.OCI1Index) and access the public members +// directly. +type List interface { + // MIMEType returns the MIME type of this particular manifest list. + MIMEType() string + + // Instances returns a list of the manifests that this list knows of, other than its own. + Instances() []digest.Digest + + // Update information about the list's instances. The length of the passed-in slice must + // match the length of the list of instances which the list already contains, and every field + // must be specified. + UpdateInstances([]ListUpdate) error + + // Instance returns the size and MIME type of a particular instance in the list. + Instance(digest.Digest) (ListUpdate, error) + + // ChooseInstance selects which manifest is most appropriate for the platform described by the + // SystemContext, or for the current platform if the SystemContext doesn't specify any details. + ChooseInstance(ctx *types.SystemContext) (digest.Digest, error) + + // Serialize returns the list in a blob format. + // NOTE: Serialize() does not in general reproduce the original blob if this object was loaded + // from, even if no modifications were made! + Serialize() ([]byte, error) + + // ConvertToMIMEType returns the list rebuilt to the specified MIME type, or an error. + ConvertToMIMEType(mimeType string) (List, error) + + // Clone returns a deep copy of this list and its contents. + Clone() List +} + +// ListUpdate includes the fields which a List's UpdateInstances() method will modify. +type ListUpdate struct { + Digest digest.Digest + Size int64 + MediaType string +} + +// dupStringSlice returns a deep copy of a slice of strings, or nil if the +// source slice is empty. +func dupStringSlice(list []string) []string { + if len(list) == 0 { + return nil + } + dup := make([]string, len(list)) + for i := range list { + dup[i] = list[i] + } + return dup +} + +// dupStringStringMap returns a deep copy of a map[string]string, or nil if the +// passed-in map is nil or has no keys. +func dupStringStringMap(m map[string]string) map[string]string { + if len(m) == 0 { + return nil + } + result := make(map[string]string) + for k, v := range m { + result[k] = v + } + return result +} + +// ListFromBlob parses a list of manifests. +func ListFromBlob(manifest []byte, manifestMIMEType string) (List, error) { + normalized := NormalizedMIMEType(manifestMIMEType) + switch normalized { + case DockerV2ListMediaType: + return Schema2ListFromManifest(manifest) + case imgspecv1.MediaTypeImageIndex: + return OCI1IndexFromManifest(manifest) + case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType, imgspecv1.MediaTypeImageManifest, DockerV2Schema2MediaType: + return nil, fmt.Errorf("Treating single images as manifest lists is not implemented") + } + return nil, fmt.Errorf("Unimplemented manifest list MIME type %s (normalized as %s)", manifestMIMEType, normalized) +} + +// ConvertListToMIMEType converts the passed-in manifest list to a manifest +// list of the specified type. +func ConvertListToMIMEType(list List, manifestMIMEType string) (List, error) { + return list.ConvertToMIMEType(manifestMIMEType) +} diff --git a/vendor/github.com/containers/image/v4/manifest/manifest.go b/vendor/github.com/containers/image/v5/manifest/manifest.go similarity index 94% rename from vendor/github.com/containers/image/v4/manifest/manifest.go rename to vendor/github.com/containers/image/v5/manifest/manifest.go index 32af97ea81d..5b4d341d845 100644 --- a/vendor/github.com/containers/image/v4/manifest/manifest.go +++ b/vendor/github.com/containers/image/v5/manifest/manifest.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" "github.com/containers/libtrust" digest "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -52,6 +52,7 @@ var DefaultRequestedManifestMIMETypes = []string{ DockerV2Schema1SignedMediaType, DockerV2Schema1MediaType, DockerV2ListMediaType, + imgspecv1.MediaTypeImageIndex, } // Manifest is an interface for parsing, modifying image manifests in isolation. @@ -140,8 +141,11 @@ func GuessMIMEType(manifest []byte) string { if err := json.Unmarshal(manifest, &ociIndex); err != nil { return "" } - if len(ociIndex.Manifests) != 0 && ociIndex.Manifests[0].MediaType == imgspecv1.MediaTypeImageManifest { - return imgspecv1.MediaTypeImageIndex + if len(ociIndex.Manifests) != 0 { + if ociMan.Config.MediaType == "" { + return imgspecv1.MediaTypeImageIndex + } + return ociMan.Config.MediaType } return DockerV2Schema2MediaType } @@ -199,7 +203,7 @@ func AddDummyV2S1Signature(manifest []byte) ([]byte, error) { // MIMETypeIsMultiImage returns true if mimeType is a list of images func MIMETypeIsMultiImage(mimeType string) bool { - return mimeType == DockerV2ListMediaType + return mimeType == DockerV2ListMediaType || mimeType == imgspecv1.MediaTypeImageIndex } // NormalizedMIMEType returns the effective MIME type of a manifest MIME type returned by a server, @@ -213,6 +217,7 @@ func NormalizedMIMEType(input string) string { return DockerV2Schema1SignedMediaType case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType, imgspecv1.MediaTypeImageManifest, + imgspecv1.MediaTypeImageIndex, DockerV2Schema2MediaType, DockerV2ListMediaType: return input @@ -232,18 +237,19 @@ func NormalizedMIMEType(input string) string { // FromBlob returns a Manifest instance for the specified manifest blob and the corresponding MIME type func FromBlob(manblob []byte, mt string) (Manifest, error) { - switch NormalizedMIMEType(mt) { + nmt := NormalizedMIMEType(mt) + switch nmt { case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType: return Schema1FromManifest(manblob) case imgspecv1.MediaTypeImageManifest: return OCI1FromManifest(manblob) case DockerV2Schema2MediaType: return Schema2FromManifest(manblob) - case DockerV2ListMediaType: + case DockerV2ListMediaType, imgspecv1.MediaTypeImageIndex: return nil, fmt.Errorf("Treating manifest lists as individual manifests is not implemented") - default: // Note that this may not be reachable, NormalizedMIMEType has a default for unknown values. - return nil, fmt.Errorf("Unimplemented manifest MIME type %s", mt) } + // Note that this may not be reachable, NormalizedMIMEType has a default for unknown values. + return nil, fmt.Errorf("Unimplemented manifest MIME type %s (normalized as %s)", mt, nmt) } // layerInfosToStrings converts a list of layer infos, presumably obtained from a Manifest.LayerInfos() diff --git a/vendor/github.com/containers/image/v4/manifest/oci.go b/vendor/github.com/containers/image/v5/manifest/oci.go similarity index 99% rename from vendor/github.com/containers/image/v4/manifest/oci.go rename to vendor/github.com/containers/image/v5/manifest/oci.go index e483bbb1980..46c551b188b 100644 --- a/vendor/github.com/containers/image/v4/manifest/oci.go +++ b/vendor/github.com/containers/image/v5/manifest/oci.go @@ -4,8 +4,8 @@ import ( "encoding/json" "fmt" - "github.com/containers/image/v4/pkg/compression" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/pkg/compression" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/specs-go" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" diff --git a/vendor/github.com/containers/image/v5/manifest/oci_index.go b/vendor/github.com/containers/image/v5/manifest/oci_index.go new file mode 100644 index 00000000000..816503ce5ea --- /dev/null +++ b/vendor/github.com/containers/image/v5/manifest/oci_index.go @@ -0,0 +1,221 @@ +package manifest + +import ( + "encoding/json" + "fmt" + "runtime" + + "github.com/containers/image/v5/types" + "github.com/opencontainers/go-digest" + imgspec "github.com/opencontainers/image-spec/specs-go" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" +) + +// OCI1Index is just an alias for the OCI index type, but one which we can +// provide methods for. +type OCI1Index struct { + imgspecv1.Index +} + +// MIMEType returns the MIME type of this particular manifest index. +func (index *OCI1Index) MIMEType() string { + return imgspecv1.MediaTypeImageIndex +} + +// Instances returns a slice of digests of the manifests that this index knows of. +func (index *OCI1Index) Instances() []digest.Digest { + results := make([]digest.Digest, len(index.Manifests)) + for i, m := range index.Manifests { + results[i] = m.Digest + } + return results +} + +// Instance returns the ListUpdate of a particular instance in the index. +func (index *OCI1Index) Instance(instanceDigest digest.Digest) (ListUpdate, error) { + for _, manifest := range index.Manifests { + if manifest.Digest == instanceDigest { + return ListUpdate{ + Digest: manifest.Digest, + Size: manifest.Size, + MediaType: manifest.MediaType, + }, nil + } + } + return ListUpdate{}, errors.Errorf("unable to find instance %s in OCI1Index", instanceDigest) +} + +// UpdateInstances updates the sizes, digests, and media types of the manifests +// which the list catalogs. +func (index *OCI1Index) UpdateInstances(updates []ListUpdate) error { + if len(updates) != len(index.Manifests) { + return errors.Errorf("incorrect number of update entries passed to OCI1Index.UpdateInstances: expected %d, got %d", len(index.Manifests), len(updates)) + } + for i := range updates { + if err := updates[i].Digest.Validate(); err != nil { + return errors.Wrapf(err, "update %d of %d passed to OCI1Index.UpdateInstances contained an invalid digest", i+1, len(updates)) + } + index.Manifests[i].Digest = updates[i].Digest + if updates[i].Size < 0 { + return errors.Errorf("update %d of %d passed to OCI1Index.UpdateInstances had an invalid size (%d)", i+1, len(updates), updates[i].Size) + } + index.Manifests[i].Size = updates[i].Size + if updates[i].MediaType == "" { + return errors.Errorf("update %d of %d passed to OCI1Index.UpdateInstances had no media type (was %q)", i+1, len(updates), index.Manifests[i].MediaType) + } + if err := SupportedOCI1MediaType(updates[i].MediaType); err != nil && SupportedSchema2MediaType(updates[i].MediaType) != nil && updates[i].MediaType != imgspecv1.MediaTypeImageIndex { + return errors.Wrapf(err, "update %d of %d passed to OCI1Index.UpdateInstances had an unsupported media type (was %q): %q", i+1, len(updates), index.Manifests[i].MediaType, updates[i].MediaType) + } + index.Manifests[i].MediaType = updates[i].MediaType + } + return nil +} + +// ChooseInstance parses blob as an oci v1 manifest index, and returns the digest +// of the image which is appropriate for the current environment. +func (index *OCI1Index) ChooseInstance(ctx *types.SystemContext) (digest.Digest, error) { + wantedArch := runtime.GOARCH + if ctx != nil && ctx.ArchitectureChoice != "" { + wantedArch = ctx.ArchitectureChoice + } + wantedOS := runtime.GOOS + if ctx != nil && ctx.OSChoice != "" { + wantedOS = ctx.OSChoice + } + + for _, d := range index.Manifests { + if d.Platform != nil && d.Platform.Architecture == wantedArch && d.Platform.OS == wantedOS { + return d.Digest, nil + } + } + for _, d := range index.Manifests { + if d.Platform == nil { + return d.Digest, nil + } + } + return "", fmt.Errorf("no image found in image index for architecture %s, OS %s", wantedArch, wantedOS) +} + +// Serialize returns the index in a blob format. +// NOTE: Serialize() does not in general reproduce the original blob if this object was loaded from one, even if no modifications were made! +func (index *OCI1Index) Serialize() ([]byte, error) { + buf, err := json.Marshal(index) + if err != nil { + return nil, errors.Wrapf(err, "error marshaling OCI1Index %#v", index) + } + return buf, nil +} + +// OCI1IndexFromComponents creates an OCI1 image index instance from the +// supplied data. +func OCI1IndexFromComponents(components []imgspecv1.Descriptor, annotations map[string]string) *OCI1Index { + index := OCI1Index{ + imgspecv1.Index{ + Versioned: imgspec.Versioned{SchemaVersion: 2}, + Manifests: make([]imgspecv1.Descriptor, len(components)), + Annotations: dupStringStringMap(annotations), + }, + } + for i, component := range components { + var platform *imgspecv1.Platform + if component.Platform != nil { + platform = &imgspecv1.Platform{ + Architecture: component.Platform.Architecture, + OS: component.Platform.OS, + OSVersion: component.Platform.OSVersion, + OSFeatures: dupStringSlice(component.Platform.OSFeatures), + Variant: component.Platform.Variant, + } + } + m := imgspecv1.Descriptor{ + MediaType: component.MediaType, + Size: component.Size, + Digest: component.Digest, + URLs: dupStringSlice(component.URLs), + Annotations: dupStringStringMap(component.Annotations), + Platform: platform, + } + index.Manifests[i] = m + } + return &index +} + +// OCI1IndexClone creates a deep copy of the passed-in index. +func OCI1IndexClone(index *OCI1Index) *OCI1Index { + return OCI1IndexFromComponents(index.Manifests, index.Annotations) +} + +// ToOCI1Index returns the index encoded as an OCI1 index. +func (index *OCI1Index) ToOCI1Index() (*OCI1Index, error) { + return OCI1IndexClone(index), nil +} + +// ToSchema2List returns the index encoded as a Schema2 list. +func (index *OCI1Index) ToSchema2List() (*Schema2List, error) { + components := make([]Schema2ManifestDescriptor, 0, len(index.Manifests)) + for _, manifest := range index.Manifests { + platform := manifest.Platform + if platform == nil { + platform = &imgspecv1.Platform{ + OS: runtime.GOOS, + Architecture: runtime.GOARCH, + } + } + converted := Schema2ManifestDescriptor{ + Schema2Descriptor{ + MediaType: manifest.MediaType, + Size: manifest.Size, + Digest: manifest.Digest, + URLs: dupStringSlice(manifest.URLs), + }, + Schema2PlatformSpec{ + OS: platform.OS, + Architecture: platform.Architecture, + OSFeatures: dupStringSlice(platform.OSFeatures), + OSVersion: platform.OSVersion, + Variant: platform.Variant, + }, + } + components = append(components, converted) + } + s2 := Schema2ListFromComponents(components) + return s2, nil +} + +// OCI1IndexFromManifest creates an OCI1 manifest index instance from marshalled +// JSON, presumably generated by encoding a OCI1 manifest index. +func OCI1IndexFromManifest(manifest []byte) (*OCI1Index, error) { + index := OCI1Index{ + Index: imgspecv1.Index{ + Versioned: imgspec.Versioned{SchemaVersion: 2}, + Manifests: []imgspecv1.Descriptor{}, + Annotations: make(map[string]string), + }, + } + if err := json.Unmarshal(manifest, &index); err != nil { + return nil, errors.Wrapf(err, "error unmarshaling OCI1Index %q", string(manifest)) + } + return &index, nil +} + +// Clone returns a deep copy of this list and its contents. +func (index *OCI1Index) Clone() List { + return OCI1IndexClone(index) +} + +// ConvertToMIMEType converts the passed-in image index to a manifest list of +// the specified type. +func (index *OCI1Index) ConvertToMIMEType(manifestMIMEType string) (List, error) { + switch normalized := NormalizedMIMEType(manifestMIMEType); normalized { + case DockerV2ListMediaType: + return index.ToSchema2List() + case imgspecv1.MediaTypeImageIndex: + return index.Clone(), nil + case DockerV2Schema1MediaType, DockerV2Schema1SignedMediaType, imgspecv1.MediaTypeImageManifest, DockerV2Schema2MediaType: + return nil, fmt.Errorf("Can not convert image index to MIME type %q, which is not a list type", manifestMIMEType) + default: + // Note that this may not be reachable, NormalizedMIMEType has a default for unknown values. + return nil, fmt.Errorf("Unimplemented manifest MIME type %s", manifestMIMEType) + } +} diff --git a/vendor/github.com/containers/image/v4/oci/archive/oci_dest.go b/vendor/github.com/containers/image/v5/oci/archive/oci_dest.go similarity index 83% rename from vendor/github.com/containers/image/v4/oci/archive/oci_dest.go rename to vendor/github.com/containers/image/v5/oci/archive/oci_dest.go index 2455ed575af..164d5522d9c 100644 --- a/vendor/github.com/containers/image/v4/oci/archive/oci_dest.go +++ b/vendor/github.com/containers/image/v5/oci/archive/oci_dest.go @@ -5,8 +5,9 @@ import ( "io" "os" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" "github.com/containers/storage/pkg/archive" + digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" ) @@ -105,19 +106,26 @@ func (d *ociArchiveImageDestination) TryReusingBlob(ctx context.Context, info ty return d.unpackedDest.TryReusingBlob(ctx, info, cache, canSubstitute) } -// PutManifest writes manifest to the destination -func (d *ociArchiveImageDestination) PutManifest(ctx context.Context, m []byte) error { - return d.unpackedDest.PutManifest(ctx, m) +// PutManifest writes the manifest to the destination. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to overwrite the manifest for (when +// the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list. +// It is expected but not enforced that the instanceDigest, when specified, matches the digest of `manifest` as generated +// by `manifest.Digest()`. +func (d *ociArchiveImageDestination) PutManifest(ctx context.Context, m []byte, instanceDigest *digest.Digest) error { + return d.unpackedDest.PutManifest(ctx, m, instanceDigest) } -func (d *ociArchiveImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error { - return d.unpackedDest.PutSignatures(ctx, signatures) +// PutSignatures writes a set of signatures to the destination. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to write or overwrite the signatures for +// (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list. +func (d *ociArchiveImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error { + return d.unpackedDest.PutSignatures(ctx, signatures, instanceDigest) } // Commit marks the process of storing the image as successful and asks for the image to be persisted // after the directory is made, it is tarred up into a file and the directory is deleted -func (d *ociArchiveImageDestination) Commit(ctx context.Context) error { - if err := d.unpackedDest.Commit(ctx); err != nil { +func (d *ociArchiveImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error { + if err := d.unpackedDest.Commit(ctx, unparsedToplevel); err != nil { return errors.Wrapf(err, "error storing image %q", d.ref.image) } diff --git a/vendor/github.com/containers/image/v4/oci/archive/oci_src.go b/vendor/github.com/containers/image/v5/oci/archive/oci_src.go similarity index 81% rename from vendor/github.com/containers/image/v4/oci/archive/oci_src.go rename to vendor/github.com/containers/image/v5/oci/archive/oci_src.go index 8a479883fa0..33a41d44b01 100644 --- a/vendor/github.com/containers/image/v4/oci/archive/oci_src.go +++ b/vendor/github.com/containers/image/v5/oci/archive/oci_src.go @@ -4,8 +4,8 @@ import ( "context" "io" - ocilayout "github.com/containers/image/v4/oci/layout" - "github.com/containers/image/v4/types" + ocilayout "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/types" digest "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -96,7 +96,14 @@ func (s *ociArchiveImageSource) GetSignatures(ctx context.Context, instanceDiges return s.unpackedSrc.GetSignatures(ctx, instanceDigest) } -// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified. -func (s *ociArchiveImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { - return nil, nil +// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer +// blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob() +// to read the image's layers. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for +// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list +// (e.g. if the source never returns manifest lists). +// The Digest field is guaranteed to be provided; Size may be -1. +// WARNING: The list may contain duplicates, and they are semantically relevant. +func (s *ociArchiveImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) { + return s.unpackedSrc.LayerInfosForCopy(ctx, instanceDigest) } diff --git a/vendor/github.com/containers/image/v4/oci/archive/oci_transport.go b/vendor/github.com/containers/image/v5/oci/archive/oci_transport.go similarity index 94% rename from vendor/github.com/containers/image/v4/oci/archive/oci_transport.go rename to vendor/github.com/containers/image/v5/oci/archive/oci_transport.go index c8808ecb568..2d72a6fee8f 100644 --- a/vendor/github.com/containers/image/v4/oci/archive/oci_transport.go +++ b/vendor/github.com/containers/image/v5/oci/archive/oci_transport.go @@ -7,14 +7,14 @@ import ( "os" "strings" - "github.com/containers/image/v4/directory/explicitfilepath" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/image" - "github.com/containers/image/v4/internal/tmpdir" - "github.com/containers/image/v4/oci/internal" - ocilayout "github.com/containers/image/v4/oci/layout" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/directory/explicitfilepath" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/internal/tmpdir" + "github.com/containers/image/v5/oci/internal" + ocilayout "github.com/containers/image/v5/oci/layout" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containers/image/v4/oci/internal/oci_util.go b/vendor/github.com/containers/image/v5/oci/internal/oci_util.go similarity index 100% rename from vendor/github.com/containers/image/v4/oci/internal/oci_util.go rename to vendor/github.com/containers/image/v5/oci/internal/oci_util.go diff --git a/vendor/github.com/containers/image/v4/oci/layout/oci_dest.go b/vendor/github.com/containers/image/v5/oci/layout/oci_dest.go similarity index 78% rename from vendor/github.com/containers/image/v4/oci/layout/oci_dest.go rename to vendor/github.com/containers/image/v5/oci/layout/oci_dest.go index 20925d3dca9..370e8d2cd28 100644 --- a/vendor/github.com/containers/image/v4/oci/layout/oci_dest.go +++ b/vendor/github.com/containers/image/v5/oci/layout/oci_dest.go @@ -9,8 +9,8 @@ import ( "path/filepath" "runtime" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" digest "github.com/opencontainers/go-digest" imgspec "github.com/opencontainers/image-spec/specs-go" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -38,6 +38,7 @@ func newImageDestination(sys *types.SystemContext, ref ociReference) (types.Imag Versioned: imgspec.Versioned{ SchemaVersion: 2, }, + Annotations: make(map[string]string), } } @@ -73,6 +74,7 @@ func (d *ociImageDestination) Close() error { func (d *ociImageDestination) SupportedManifestMIMETypes() []string { return []string{ imgspecv1.MediaTypeImageManifest, + imgspecv1.MediaTypeImageIndex, } } @@ -205,20 +207,27 @@ func (d *ociImageDestination) TryReusingBlob(ctx context.Context, info types.Blo return true, types.BlobInfo{Digest: info.Digest, Size: finfo.Size()}, nil } -// PutManifest writes manifest to the destination. +// PutManifest writes a manifest to the destination. Per our list of supported manifest MIME types, +// this should be either an OCI manifest (possibly converted to this format by the caller) or index, +// neither of which we'll need to modify further. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to overwrite the manifest for (when +// the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list. +// It is expected but not enforced that the instanceDigest, when specified, matches the digest of `manifest` as generated +// by `manifest.Digest()`. // FIXME? This should also receive a MIME type if known, to differentiate between schema versions. // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema), // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError. -func (d *ociImageDestination) PutManifest(ctx context.Context, m []byte) error { - digest, err := manifest.Digest(m) - if err != nil { - return err +func (d *ociImageDestination) PutManifest(ctx context.Context, m []byte, instanceDigest *digest.Digest) error { + var digest digest.Digest + var err error + if instanceDigest != nil { + digest = *instanceDigest + } else { + digest, err = manifest.Digest(m) + if err != nil { + return err + } } - desc := imgspecv1.Descriptor{} - desc.Digest = digest - // TODO(runcom): beaware and add support for OCI manifest list - desc.MediaType = imgspecv1.MediaTypeImageManifest - desc.Size = int64(len(m)) blobPath, err := d.ref.blobPath(digest, d.sharedBlobDir) if err != nil { @@ -231,32 +240,59 @@ func (d *ociImageDestination) PutManifest(ctx context.Context, m []byte) error { return err } - if d.ref.image != "" { - annotations := make(map[string]string) - annotations["org.opencontainers.image.ref.name"] = d.ref.image - desc.Annotations = annotations + if instanceDigest != nil { + return nil } - desc.Platform = &imgspecv1.Platform{ - Architecture: runtime.GOARCH, - OS: runtime.GOOS, + + // If we had platform information, we'd build an imgspecv1.Platform structure here. + + // Start filling out the descriptor for this entry + desc := imgspecv1.Descriptor{} + desc.Digest = digest + desc.Size = int64(len(m)) + if d.ref.image != "" { + desc.Annotations = make(map[string]string) + desc.Annotations[imgspecv1.AnnotationRefName] = d.ref.image } + + // If we knew the MIME type, we wouldn't have to guess here. + desc.MediaType = manifest.GuessMIMEType(m) + d.addManifest(&desc) return nil } func (d *ociImageDestination) addManifest(desc *imgspecv1.Descriptor) { + // If the new entry has a name, remove any conflicting names which we already have. + if desc.Annotations != nil && desc.Annotations[imgspecv1.AnnotationRefName] != "" { + // The name is being set on a new entry, so remove any older ones that had the same name. + // We might be storing an index and all of its component images, and we'll want to attach + // the name to the last one, which is the index. + for i, manifest := range d.index.Manifests { + if manifest.Annotations[imgspecv1.AnnotationRefName] == desc.Annotations[imgspecv1.AnnotationRefName] { + delete(d.index.Manifests[i].Annotations, imgspecv1.AnnotationRefName) + break + } + } + } + // If it has the same digest as another entry in the index, we already overwrote the file, + // so just pick up the other information. for i, manifest := range d.index.Manifests { - if manifest.Annotations["org.opencontainers.image.ref.name"] == desc.Annotations["org.opencontainers.image.ref.name"] { - // TODO Should there first be a cleanup based on the descriptor we are going to replace? + if manifest.Digest == desc.Digest { + // Replace it completely. d.index.Manifests[i] = *desc return } } + // It's a new entry to be added to the index. d.index.Manifests = append(d.index.Manifests, *desc) } -func (d *ociImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error { +// PutSignatures would add the given signatures to the oci layout (currently not supported). +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to write or overwrite the signatures for +// (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list. +func (d *ociImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error { if len(signatures) != 0 { return errors.Errorf("Pushing signatures for OCI images is not supported") } @@ -267,7 +303,7 @@ func (d *ociImageDestination) PutSignatures(ctx context.Context, signatures [][] // WARNING: This does not have any transactional semantics: // - Uploaded data MAY be visible to others before Commit() is called // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed) -func (d *ociImageDestination) Commit(ctx context.Context) error { +func (d *ociImageDestination) Commit(context.Context, types.UnparsedImage) error { if err := ioutil.WriteFile(d.ref.ociLayoutPath(), []byte(`{"imageLayoutVersion": "1.0.0"}`), 0644); err != nil { return err } diff --git a/vendor/github.com/containers/image/v4/oci/layout/oci_src.go b/vendor/github.com/containers/image/v5/oci/layout/oci_src.go similarity index 78% rename from vendor/github.com/containers/image/v4/oci/layout/oci_src.go rename to vendor/github.com/containers/image/v5/oci/layout/oci_src.go index dd6c6c4a66a..f515203df76 100644 --- a/vendor/github.com/containers/image/v4/oci/layout/oci_src.go +++ b/vendor/github.com/containers/image/v5/oci/layout/oci_src.go @@ -8,8 +8,9 @@ import ( "os" "strconv" - "github.com/containers/image/v4/pkg/tlsclientconfig" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/tlsclientconfig" + "github.com/containers/image/v5/types" "github.com/docker/go-connections/tlsconfig" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -18,6 +19,7 @@ import ( type ociImageSource struct { ref ociReference + index *imgspecv1.Index descriptor imgspecv1.Descriptor client *http.Client sharedBlobDir string @@ -41,7 +43,11 @@ func newImageSource(sys *types.SystemContext, ref ociReference) (types.ImageSour if err != nil { return nil, err } - d := &ociImageSource{ref: ref, descriptor: descriptor, client: client} + index, err := ref.getIndex() + if err != nil { + return nil, err + } + d := &ociImageSource{ref: ref, index: index, descriptor: descriptor, client: client} if sys != nil { // TODO(jonboulle): check dir existence? d.sharedBlobDir = sys.OCISharedBlobDirPath @@ -66,28 +72,33 @@ func (s *ociImageSource) Close() error { func (s *ociImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { var dig digest.Digest var mimeType string + var err error + if instanceDigest == nil { dig = digest.Digest(s.descriptor.Digest) mimeType = s.descriptor.MediaType } else { dig = *instanceDigest - // XXX: instanceDigest means that we don't immediately have the context of what - // mediaType the manifest has. In OCI this means that we don't know - // what reference it came from, so we just *assume* that its - // MediaTypeImageManifest. - // FIXME: We should actually be able to look up the manifest in the index, - // and see the MIME type there. - mimeType = imgspecv1.MediaTypeImageManifest + for _, md := range s.index.Manifests { + if md.Digest == dig { + mimeType = md.MediaType + break + } + } } manifestPath, err := s.ref.blobPath(dig, s.sharedBlobDir) if err != nil { return nil, "", err } + m, err := ioutil.ReadFile(manifestPath) if err != nil { return nil, "", err } + if mimeType == "" { + mimeType = manifest.GuessMIMEType(m) + } return m, mimeType, nil } @@ -157,8 +168,15 @@ func (s *ociImageSource) getExternalBlob(ctx context.Context, urls []string) (io return nil, 0, errWrap } -// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified. -func (s *ociImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { +// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer +// blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob() +// to read the image's layers. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for +// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list +// (e.g. if the source never returns manifest lists). +// The Digest field is guaranteed to be provided; Size may be -1. +// WARNING: The list may contain duplicates, and they are semantically relevant. +func (s *ociImageSource) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) { return nil, nil } diff --git a/vendor/github.com/containers/image/v4/oci/layout/oci_transport.go b/vendor/github.com/containers/image/v5/oci/layout/oci_transport.go similarity index 95% rename from vendor/github.com/containers/image/v4/oci/layout/oci_transport.go rename to vendor/github.com/containers/image/v5/oci/layout/oci_transport.go index 259852b4dc5..c662c9a7a08 100644 --- a/vendor/github.com/containers/image/v4/oci/layout/oci_transport.go +++ b/vendor/github.com/containers/image/v5/oci/layout/oci_transport.go @@ -8,12 +8,12 @@ import ( "path/filepath" "strings" - "github.com/containers/image/v4/directory/explicitfilepath" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/image" - "github.com/containers/image/v4/oci/internal" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/directory/explicitfilepath" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/oci/internal" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -195,10 +195,10 @@ func (ref ociReference) getManifestDescriptor() (imgspecv1.Descriptor, error) { } else { // if image specified, look through all manifests for a match for _, md := range index.Manifests { - if md.MediaType != imgspecv1.MediaTypeImageManifest { + if md.MediaType != imgspecv1.MediaTypeImageManifest && md.MediaType != imgspecv1.MediaTypeImageIndex { continue } - refName, ok := md.Annotations["org.opencontainers.image.ref.name"] + refName, ok := md.Annotations[imgspecv1.AnnotationRefName] if !ok { continue } diff --git a/vendor/github.com/containers/image/v4/openshift/openshift-copies.go b/vendor/github.com/containers/image/v5/openshift/openshift-copies.go similarity index 100% rename from vendor/github.com/containers/image/v4/openshift/openshift-copies.go rename to vendor/github.com/containers/image/v5/openshift/openshift-copies.go diff --git a/vendor/github.com/containers/image/v4/openshift/openshift.go b/vendor/github.com/containers/image/v5/openshift/openshift.go similarity index 90% rename from vendor/github.com/containers/image/v4/openshift/openshift.go rename to vendor/github.com/containers/image/v5/openshift/openshift.go index 51fff6269c4..016de480349 100644 --- a/vendor/github.com/containers/image/v4/openshift/openshift.go +++ b/vendor/github.com/containers/image/v5/openshift/openshift.go @@ -12,11 +12,11 @@ import ( "net/url" "strings" - "github.com/containers/image/v4/docker" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" - "github.com/containers/image/v4/version" + "github.com/containers/image/v5/docker" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" + "github.com/containers/image/v5/version" "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -231,16 +231,16 @@ func (s *openshiftImageSource) GetBlob(ctx context.Context, info types.BlobInfo, // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list // (e.g. if the source never returns manifest lists). func (s *openshiftImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { - var imageName string + var imageStreamImageName string if instanceDigest == nil { if err := s.ensureImageIsResolved(ctx); err != nil { return nil, err } - imageName = s.imageStreamImageName + imageStreamImageName = s.imageStreamImageName } else { - imageName = instanceDigest.String() + imageStreamImageName = instanceDigest.String() } - image, err := s.client.getImage(ctx, imageName) + image, err := s.client.getImage(ctx, imageStreamImageName) if err != nil { return nil, err } @@ -253,8 +253,15 @@ func (s *openshiftImageSource) GetSignatures(ctx context.Context, instanceDigest return sigs, nil } -// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified. -func (s *openshiftImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { +// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer +// blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob() +// to read the image's layers. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for +// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list +// (e.g. if the source never returns manifest lists). +// The Digest field is guaranteed to be provided; Size may be -1. +// WARNING: The list may contain duplicates, and they are semantically relevant. +func (s *openshiftImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) { return nil, nil } @@ -414,20 +421,28 @@ func (d *openshiftImageDestination) TryReusingBlob(ctx context.Context, info typ // FIXME? This should also receive a MIME type if known, to differentiate between schema versions. // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema), // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError. -func (d *openshiftImageDestination) PutManifest(ctx context.Context, m []byte) error { - manifestDigest, err := manifest.Digest(m) - if err != nil { - return err +func (d *openshiftImageDestination) PutManifest(ctx context.Context, m []byte, instanceDigest *digest.Digest) error { + if instanceDigest == nil { + manifestDigest, err := manifest.Digest(m) + if err != nil { + return err + } + d.imageStreamImageName = manifestDigest.String() } - d.imageStreamImageName = manifestDigest.String() - - return d.docker.PutManifest(ctx, m) + return d.docker.PutManifest(ctx, m, instanceDigest) } -func (d *openshiftImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error { - if d.imageStreamImageName == "" { - return errors.Errorf("Internal error: Unknown manifest digest, can't add signatures") +func (d *openshiftImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error { + var imageStreamName string + if instanceDigest == nil { + if d.imageStreamImageName == "" { + return errors.Errorf("Internal error: Unknown manifest digest, can't add signatures") + } + imageStreamName = d.imageStreamImageName + } else { + imageStreamName = instanceDigest.String() } + // Because image signatures are a shared resource in Atomic Registry, the default upload // always adds signatures. Eventually we should also allow removing signatures. @@ -435,7 +450,7 @@ func (d *openshiftImageDestination) PutSignatures(ctx context.Context, signature return nil // No need to even read the old state. } - image, err := d.client.getImage(ctx, d.imageStreamImageName) + image, err := d.client.getImage(ctx, imageStreamName) if err != nil { return err } @@ -460,7 +475,7 @@ sigExists: if err != nil || n != 16 { return errors.Wrapf(err, "Error generating random signature len %d", n) } - signatureName = fmt.Sprintf("%s@%032x", d.imageStreamImageName, randBytes) + signatureName = fmt.Sprintf("%s@%032x", imageStreamName, randBytes) if _, ok := existingSigNames[signatureName]; !ok { break } @@ -489,8 +504,8 @@ sigExists: // WARNING: This does not have any transactional semantics: // - Uploaded data MAY be visible to others before Commit() is called // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed) -func (d *openshiftImageDestination) Commit(ctx context.Context) error { - return d.docker.Commit(ctx) +func (d *openshiftImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error { + return d.docker.Commit(ctx, unparsedToplevel) } // These structs are subsets of github.com/openshift/origin/pkg/image/api/v1 and its dependencies. diff --git a/vendor/github.com/containers/image/v4/openshift/openshift_transport.go b/vendor/github.com/containers/image/v5/openshift/openshift_transport.go similarity index 96% rename from vendor/github.com/containers/image/v4/openshift/openshift_transport.go rename to vendor/github.com/containers/image/v5/openshift/openshift_transport.go index f00c94561b4..6bbb43be283 100644 --- a/vendor/github.com/containers/image/v4/openshift/openshift_transport.go +++ b/vendor/github.com/containers/image/v5/openshift/openshift_transport.go @@ -6,11 +6,11 @@ import ( "regexp" "strings" - "github.com/containers/image/v4/docker/policyconfiguration" - "github.com/containers/image/v4/docker/reference" - genericImage "github.com/containers/image/v4/image" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/policyconfiguration" + "github.com/containers/image/v5/docker/reference" + genericImage "github.com/containers/image/v5/image" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containers/image/v4/ostree/ostree_dest.go b/vendor/github.com/containers/image/v5/ostree/ostree_dest.go similarity index 94% rename from vendor/github.com/containers/image/v4/ostree/ostree_dest.go rename to vendor/github.com/containers/image/v5/ostree/ostree_dest.go index 9e1436e29eb..c442b4d2ee9 100644 --- a/vendor/github.com/containers/image/v4/ostree/ostree_dest.go +++ b/vendor/github.com/containers/image/v5/ostree/ostree_dest.go @@ -20,8 +20,8 @@ import ( "time" "unsafe" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" "github.com/containers/storage/pkg/archive" "github.com/klauspost/pgzip" "github.com/opencontainers/go-digest" @@ -376,10 +376,16 @@ func (d *ostreeImageDestination) TryReusingBlob(ctx context.Context, info types. } // PutManifest writes manifest to the destination. +// The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so +// there can be no secondary manifests. // FIXME? This should also receive a MIME type if known, to differentiate between schema versions. // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema), // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError. -func (d *ostreeImageDestination) PutManifest(ctx context.Context, manifestBlob []byte) error { +func (d *ostreeImageDestination) PutManifest(ctx context.Context, manifestBlob []byte, instanceDigest *digest.Digest) error { + if instanceDigest != nil { + return errors.New(`Manifest lists are not supported by "ostree:"`) + } + d.manifest = string(manifestBlob) if err := json.Unmarshal(manifestBlob, &d.schema); err != nil { @@ -400,7 +406,14 @@ func (d *ostreeImageDestination) PutManifest(ctx context.Context, manifestBlob [ return ioutil.WriteFile(manifestPath, manifestBlob, 0644) } -func (d *ostreeImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error { +// PutSignatures writes signatures to the destination. +// The instanceDigest value is expected to always be nil, because this transport does not support manifest lists, so +// there can be no secondary manifests. +func (d *ostreeImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error { + if instanceDigest != nil { + return errors.New(`Manifest lists are not supported by "ostree:"`) + } + path := filepath.Join(d.tmpDirPath, d.ref.signaturePath(0)) if err := ensureParentDirectoryExists(path); err != nil { return err @@ -416,7 +429,7 @@ func (d *ostreeImageDestination) PutSignatures(ctx context.Context, signatures [ return nil } -func (d *ostreeImageDestination) Commit(ctx context.Context) error { +func (d *ostreeImageDestination) Commit(context.Context, types.UnparsedImage) error { runtime.LockOSThread() defer runtime.UnlockOSThread() diff --git a/vendor/github.com/containers/image/v4/ostree/ostree_src.go b/vendor/github.com/containers/image/v5/ostree/ostree_src.go similarity index 88% rename from vendor/github.com/containers/image/v4/ostree/ostree_src.go rename to vendor/github.com/containers/image/v5/ostree/ostree_src.go index ecb6e3f84ae..4948ec66416 100644 --- a/vendor/github.com/containers/image/v4/ostree/ostree_src.go +++ b/vendor/github.com/containers/image/v5/ostree/ostree_src.go @@ -13,8 +13,8 @@ import ( "strings" "unsafe" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" "github.com/containers/storage/pkg/ioutils" "github.com/klauspost/pgzip" digest "github.com/opencontainers/go-digest" @@ -98,9 +98,11 @@ func (s *ostreeImageSource) getTarSplitData(blob string) ([]byte, error) { // GetManifest returns the image's manifest along with its MIME type (which may be empty when it can't be determined but the manifest is available). // It may use a remote (= slow) service. +// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil, +// as the primary manifest can not be a list, so there can be non-default instances. func (s *ostreeImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) ([]byte, string, error) { if instanceDigest != nil { - return nil, "", errors.Errorf(`Manifest lists are not supported by "ostree:"`) + return nil, "", errors.New(`Manifest lists are not supported by "ostree:"`) } if s.repo == nil { repo, err := openRepo(s.ref.repo) @@ -275,7 +277,7 @@ func (s *ostreeImageSource) GetBlob(ctx context.Context, info types.BlobInfo, ca // Ensure s.compressed is initialized. It is build by LayerInfosForCopy. if s.compressed == nil { - _, err := s.LayerInfosForCopy(ctx) + _, err := s.LayerInfosForCopy(ctx, nil) if err != nil { return nil, -1, err } @@ -337,9 +339,12 @@ func (s *ostreeImageSource) GetBlob(ctx context.Context, info types.BlobInfo, ca return rc, layerSize, nil } +// GetSignatures returns the image's signatures. It may use a remote (= slow) service. +// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil, +// as there can be no secondary manifests. func (s *ostreeImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { if instanceDigest != nil { - return nil, errors.New("manifest lists are not supported by this transport") + return nil, errors.New(`Manifest lists are not supported by "ostree:"`) } lenSignatures, err := s.getLenSignatures() if err != nil { @@ -372,9 +377,18 @@ func (s *ostreeImageSource) GetSignatures(ctx context.Context, instanceDigest *d return signatures, nil } -// LayerInfosForCopy() returns the list of layer blobs that make up the root filesystem of -// the image, after they've been decompressed. -func (s *ostreeImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { +// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer +// blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob() +// to read the image's layers. +// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil, +// as the primary manifest can not be a list, so there can be secondary manifests. +// The Digest field is guaranteed to be provided; Size may be -1. +// WARNING: The list may contain duplicates, and they are semantically relevant. +func (s *ostreeImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) { + if instanceDigest != nil { + return nil, errors.New(`Manifest lists are not supported by "ostree:"`) + } + updatedBlobInfos := []types.BlobInfo{} manifestBlob, manifestType, err := s.GetManifest(ctx, nil) if err != nil { diff --git a/vendor/github.com/containers/image/v4/ostree/ostree_transport.go b/vendor/github.com/containers/image/v5/ostree/ostree_transport.go similarity index 97% rename from vendor/github.com/containers/image/v4/ostree/ostree_transport.go rename to vendor/github.com/containers/image/v5/ostree/ostree_transport.go index d720cb7ac42..a55147b85e3 100644 --- a/vendor/github.com/containers/image/v4/ostree/ostree_transport.go +++ b/vendor/github.com/containers/image/v5/ostree/ostree_transport.go @@ -11,11 +11,11 @@ import ( "regexp" "strings" - "github.com/containers/image/v4/directory/explicitfilepath" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/image" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/directory/explicitfilepath" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containers/image/v4/pkg/blobinfocache/boltdb/boltdb.go b/vendor/github.com/containers/image/v5/pkg/blobinfocache/boltdb/boltdb.go similarity index 99% rename from vendor/github.com/containers/image/v4/pkg/blobinfocache/boltdb/boltdb.go rename to vendor/github.com/containers/image/v5/pkg/blobinfocache/boltdb/boltdb.go index 85eb7d6f175..9c9a17a5840 100644 --- a/vendor/github.com/containers/image/v4/pkg/blobinfocache/boltdb/boltdb.go +++ b/vendor/github.com/containers/image/v5/pkg/blobinfocache/boltdb/boltdb.go @@ -7,8 +7,8 @@ import ( "sync" "time" - "github.com/containers/image/v4/pkg/blobinfocache/internal/prioritize" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/pkg/blobinfocache/internal/prioritize" + "github.com/containers/image/v5/types" bolt "github.com/etcd-io/bbolt" "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" diff --git a/vendor/github.com/containers/image/v4/pkg/blobinfocache/default.go b/vendor/github.com/containers/image/v5/pkg/blobinfocache/default.go similarity index 94% rename from vendor/github.com/containers/image/v4/pkg/blobinfocache/default.go rename to vendor/github.com/containers/image/v5/pkg/blobinfocache/default.go index af136c36d1c..952bcf5a12c 100644 --- a/vendor/github.com/containers/image/v4/pkg/blobinfocache/default.go +++ b/vendor/github.com/containers/image/v5/pkg/blobinfocache/default.go @@ -6,9 +6,9 @@ import ( "path/filepath" "strconv" - "github.com/containers/image/v4/pkg/blobinfocache/boltdb" - "github.com/containers/image/v4/pkg/blobinfocache/memory" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/pkg/blobinfocache/boltdb" + "github.com/containers/image/v5/pkg/blobinfocache/memory" + "github.com/containers/image/v5/types" "github.com/sirupsen/logrus" ) diff --git a/vendor/github.com/containers/image/v4/pkg/blobinfocache/internal/prioritize/prioritize.go b/vendor/github.com/containers/image/v5/pkg/blobinfocache/internal/prioritize/prioritize.go similarity index 99% rename from vendor/github.com/containers/image/v4/pkg/blobinfocache/internal/prioritize/prioritize.go rename to vendor/github.com/containers/image/v5/pkg/blobinfocache/internal/prioritize/prioritize.go index 7820119b09d..5deca4a82d5 100644 --- a/vendor/github.com/containers/image/v4/pkg/blobinfocache/internal/prioritize/prioritize.go +++ b/vendor/github.com/containers/image/v5/pkg/blobinfocache/internal/prioritize/prioritize.go @@ -6,7 +6,7 @@ import ( "sort" "time" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" ) diff --git a/vendor/github.com/containers/image/v4/pkg/blobinfocache/memory/memory.go b/vendor/github.com/containers/image/v5/pkg/blobinfocache/memory/memory.go similarity index 98% rename from vendor/github.com/containers/image/v4/pkg/blobinfocache/memory/memory.go rename to vendor/github.com/containers/image/v5/pkg/blobinfocache/memory/memory.go index c51b9f5cefc..8f28c66238d 100644 --- a/vendor/github.com/containers/image/v4/pkg/blobinfocache/memory/memory.go +++ b/vendor/github.com/containers/image/v5/pkg/blobinfocache/memory/memory.go @@ -5,8 +5,8 @@ import ( "sync" "time" - "github.com/containers/image/v4/pkg/blobinfocache/internal/prioritize" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/pkg/blobinfocache/internal/prioritize" + "github.com/containers/image/v5/types" digest "github.com/opencontainers/go-digest" "github.com/sirupsen/logrus" ) diff --git a/vendor/github.com/containers/image/v4/pkg/blobinfocache/none/none.go b/vendor/github.com/containers/image/v5/pkg/blobinfocache/none/none.go similarity index 98% rename from vendor/github.com/containers/image/v4/pkg/blobinfocache/none/none.go rename to vendor/github.com/containers/image/v5/pkg/blobinfocache/none/none.go index c5ce29a4504..fa1879afdb6 100644 --- a/vendor/github.com/containers/image/v4/pkg/blobinfocache/none/none.go +++ b/vendor/github.com/containers/image/v5/pkg/blobinfocache/none/none.go @@ -2,7 +2,7 @@ package none import ( - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" "github.com/opencontainers/go-digest" ) diff --git a/vendor/github.com/containers/image/v4/pkg/compression/compression.go b/vendor/github.com/containers/image/v5/pkg/compression/compression.go similarity index 97% rename from vendor/github.com/containers/image/v4/pkg/compression/compression.go rename to vendor/github.com/containers/image/v5/pkg/compression/compression.go index fd2f2154958..04d231c6d66 100644 --- a/vendor/github.com/containers/image/v4/pkg/compression/compression.go +++ b/vendor/github.com/containers/image/v5/pkg/compression/compression.go @@ -7,8 +7,8 @@ import ( "io" "io/ioutil" - "github.com/containers/image/v4/pkg/compression/internal" - "github.com/containers/image/v4/pkg/compression/types" + "github.com/containers/image/v5/pkg/compression/internal" + "github.com/containers/image/v5/pkg/compression/types" "github.com/klauspost/pgzip" "github.com/pkg/errors" "github.com/sirupsen/logrus" diff --git a/vendor/github.com/containers/image/v4/pkg/compression/internal/types.go b/vendor/github.com/containers/image/v5/pkg/compression/internal/types.go similarity index 100% rename from vendor/github.com/containers/image/v4/pkg/compression/internal/types.go rename to vendor/github.com/containers/image/v5/pkg/compression/internal/types.go diff --git a/vendor/github.com/containers/image/v4/pkg/compression/types/types.go b/vendor/github.com/containers/image/v5/pkg/compression/types/types.go similarity index 88% rename from vendor/github.com/containers/image/v4/pkg/compression/types/types.go rename to vendor/github.com/containers/image/v5/pkg/compression/types/types.go index ea43dc8cd03..f96eff2e3c7 100644 --- a/vendor/github.com/containers/image/v4/pkg/compression/types/types.go +++ b/vendor/github.com/containers/image/v5/pkg/compression/types/types.go @@ -1,7 +1,7 @@ package types import ( - "github.com/containers/image/v4/pkg/compression/internal" + "github.com/containers/image/v5/pkg/compression/internal" ) // DecompressorFunc returns the decompressed stream, given a compressed stream. diff --git a/vendor/github.com/containers/image/v4/pkg/compression/zstd.go b/vendor/github.com/containers/image/v5/pkg/compression/zstd.go similarity index 100% rename from vendor/github.com/containers/image/v4/pkg/compression/zstd.go rename to vendor/github.com/containers/image/v5/pkg/compression/zstd.go diff --git a/vendor/github.com/containers/image/v4/pkg/docker/config/config.go b/vendor/github.com/containers/image/v5/pkg/docker/config/config.go similarity index 89% rename from vendor/github.com/containers/image/v4/pkg/docker/config/config.go rename to vendor/github.com/containers/image/v5/pkg/docker/config/config.go index e720dc865a9..b7dddd0d69e 100644 --- a/vendor/github.com/containers/image/v4/pkg/docker/config/config.go +++ b/vendor/github.com/containers/image/v5/pkg/docker/config/config.go @@ -9,7 +9,7 @@ import ( "path/filepath" "strings" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" helperclient "github.com/docker/docker-credential-helpers/client" "github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker/pkg/homedir" @@ -26,6 +26,11 @@ type dockerConfigFile struct { CredHelpers map[string]string `json:"credHelpers,omitempty"` } +type authPath struct { + path string + legacyFormat bool +} + var ( defaultPerUIDPathFormat = filepath.FromSlash("/run/containers/%d/auth.json") xdgRuntimeDirPath = filepath.FromSlash("containers/auth.json") @@ -84,28 +89,28 @@ func GetAuthentication(sys *types.SystemContext, registry string) (string, strin } } - dockerLegacyPath := filepath.Join(homedir.Get(), dockerLegacyHomePath) - var paths []string - pathToAuth, err := getPathToAuth(sys) + paths := []authPath{} + pathToAuth, lf, err := getPathToAuth(sys) if err == nil { - paths = append(paths, pathToAuth) + paths = append(paths, authPath{path: pathToAuth, legacyFormat: lf}) } else { // Error means that the path set for XDG_RUNTIME_DIR does not exist // but we don't want to completely fail in the case that the user is pulling a public image // Logging the error as a warning instead and moving on to pulling the image logrus.Warnf("%v: Trying to pull image in the event that it is a public image.", err) } - paths = append(paths, filepath.Join(homedir.Get(), dockerHomePath), dockerLegacyPath) + paths = append(paths, + authPath{path: filepath.Join(homedir.Get(), dockerHomePath), legacyFormat: false}, + authPath{path: filepath.Join(homedir.Get(), dockerLegacyHomePath), legacyFormat: true}) for _, path := range paths { - legacyFormat := path == dockerLegacyPath - username, password, err := findAuthentication(registry, path, legacyFormat) + username, password, err := findAuthentication(registry, path.path, path.legacyFormat) if err != nil { logrus.Debugf("Credentials not found") return "", "", err } if username != "" && password != "" { - logrus.Debugf("Returning credentials from %s", path) + logrus.Debugf("Returning credentials from %s", path.path) return username, password, nil } } @@ -163,13 +168,16 @@ func RemoveAllAuthentication(sys *types.SystemContext) error { // The path can be overriden by the user if the overwrite-path flag is set // If the flag is not set and XDG_RUNTIME_DIR is set, the auth.json file is saved in XDG_RUNTIME_DIR/containers // Otherwise, the auth.json file is stored in /run/containers/UID -func getPathToAuth(sys *types.SystemContext) (string, error) { +func getPathToAuth(sys *types.SystemContext) (string, bool, error) { if sys != nil { if sys.AuthFilePath != "" { - return sys.AuthFilePath, nil + return sys.AuthFilePath, false, nil + } + if sys.LegacyFormatAuthFilePath != "" { + return sys.LegacyFormatAuthFilePath, true, nil } if sys.RootForImplicitAbsolutePaths != "" { - return filepath.Join(sys.RootForImplicitAbsolutePaths, fmt.Sprintf(defaultPerUIDPathFormat, os.Getuid())), nil + return filepath.Join(sys.RootForImplicitAbsolutePaths, fmt.Sprintf(defaultPerUIDPathFormat, os.Getuid())), false, nil } } @@ -182,11 +190,11 @@ func getPathToAuth(sys *types.SystemContext) (string, error) { // This means the user set the XDG_RUNTIME_DIR variable and either forgot to create the directory // or made a typo while setting the environment variable, // so return an error referring to $XDG_RUNTIME_DIR instead of xdgRuntimeDirPath inside. - return "", errors.Wrapf(err, "%q directory set by $XDG_RUNTIME_DIR does not exist. Either create the directory or unset $XDG_RUNTIME_DIR.", runtimeDir) + return "", false, errors.Wrapf(err, "%q directory set by $XDG_RUNTIME_DIR does not exist. Either create the directory or unset $XDG_RUNTIME_DIR.", runtimeDir) } // else ignore err and let the caller fail accessing xdgRuntimeDirPath. - return filepath.Join(runtimeDir, xdgRuntimeDirPath), nil + return filepath.Join(runtimeDir, xdgRuntimeDirPath), false, nil } - return fmt.Sprintf(defaultPerUIDPathFormat, os.Getuid()), nil + return fmt.Sprintf(defaultPerUIDPathFormat, os.Getuid()), false, nil } // readJSONFile unmarshals the authentications stored in the auth.json file and returns it @@ -220,7 +228,7 @@ func readJSONFile(path string, legacyFormat bool) (dockerConfigFile, error) { // modifyJSON writes to auth.json if the dockerConfigFile has been updated func modifyJSON(sys *types.SystemContext, editor func(auths *dockerConfigFile) (bool, error)) error { - path, err := getPathToAuth(sys) + path, legacyFormat, err := getPathToAuth(sys) if err != nil { return err } @@ -232,6 +240,9 @@ func modifyJSON(sys *types.SystemContext, editor func(auths *dockerConfigFile) ( } } + if legacyFormat { + return fmt.Errorf("writes to %s using legacy format are not supported", path) + } auths, err := readJSONFile(path, false) if err != nil { return errors.Wrapf(err, "error reading JSON file %q", path) diff --git a/vendor/github.com/containers/image/v4/pkg/docker/config/config_linux.go b/vendor/github.com/containers/image/v5/pkg/docker/config/config_linux.go similarity index 98% rename from vendor/github.com/containers/image/v4/pkg/docker/config/config_linux.go rename to vendor/github.com/containers/image/v5/pkg/docker/config/config_linux.go index 0cd73528b0b..43f2d5a85f3 100644 --- a/vendor/github.com/containers/image/v4/pkg/docker/config/config_linux.go +++ b/vendor/github.com/containers/image/v5/pkg/docker/config/config_linux.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/containers/image/v4/internal/pkg/keyctl" + "github.com/containers/image/v5/internal/pkg/keyctl" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) diff --git a/vendor/github.com/containers/image/v4/pkg/docker/config/config_unsupported.go b/vendor/github.com/containers/image/v5/pkg/docker/config/config_unsupported.go similarity index 100% rename from vendor/github.com/containers/image/v4/pkg/docker/config/config_unsupported.go rename to vendor/github.com/containers/image/v5/pkg/docker/config/config_unsupported.go diff --git a/vendor/github.com/containers/image/v4/pkg/strslice/README.md b/vendor/github.com/containers/image/v5/pkg/strslice/README.md similarity index 100% rename from vendor/github.com/containers/image/v4/pkg/strslice/README.md rename to vendor/github.com/containers/image/v5/pkg/strslice/README.md diff --git a/vendor/github.com/containers/image/v4/pkg/strslice/strslice.go b/vendor/github.com/containers/image/v5/pkg/strslice/strslice.go similarity index 100% rename from vendor/github.com/containers/image/v4/pkg/strslice/strslice.go rename to vendor/github.com/containers/image/v5/pkg/strslice/strslice.go diff --git a/vendor/github.com/containers/image/v4/pkg/sysregistriesv2/system_registries_v2.go b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go similarity index 99% rename from vendor/github.com/containers/image/v4/pkg/sysregistriesv2/system_registries_v2.go rename to vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go index 0cd60778f76..ff802cefd9d 100644 --- a/vendor/github.com/containers/image/v4/pkg/sysregistriesv2/system_registries_v2.go +++ b/vendor/github.com/containers/image/v5/pkg/sysregistriesv2/system_registries_v2.go @@ -10,11 +10,10 @@ import ( "sync" "github.com/BurntSushi/toml" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/types" "github.com/pkg/errors" "github.com/sirupsen/logrus" - - "github.com/containers/image/v4/docker/reference" ) // systemRegistriesConfPath is the path to the system-wide registry diff --git a/vendor/github.com/containers/image/v4/pkg/tlsclientconfig/tlsclientconfig.go b/vendor/github.com/containers/image/v5/pkg/tlsclientconfig/tlsclientconfig.go similarity index 100% rename from vendor/github.com/containers/image/v4/pkg/tlsclientconfig/tlsclientconfig.go rename to vendor/github.com/containers/image/v5/pkg/tlsclientconfig/tlsclientconfig.go diff --git a/vendor/github.com/containers/image/v4/signature/docker.go b/vendor/github.com/containers/image/v5/signature/docker.go similarity index 96% rename from vendor/github.com/containers/image/v4/signature/docker.go rename to vendor/github.com/containers/image/v5/signature/docker.go index c3ac33d4843..07fdd42a9af 100644 --- a/vendor/github.com/containers/image/v4/signature/docker.go +++ b/vendor/github.com/containers/image/v5/signature/docker.go @@ -5,8 +5,8 @@ package signature import ( "fmt" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/manifest" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" "github.com/opencontainers/go-digest" ) diff --git a/vendor/github.com/containers/image/v4/signature/json.go b/vendor/github.com/containers/image/v5/signature/json.go similarity index 100% rename from vendor/github.com/containers/image/v4/signature/json.go rename to vendor/github.com/containers/image/v5/signature/json.go diff --git a/vendor/github.com/containers/image/v4/signature/mechanism.go b/vendor/github.com/containers/image/v5/signature/mechanism.go similarity index 100% rename from vendor/github.com/containers/image/v4/signature/mechanism.go rename to vendor/github.com/containers/image/v5/signature/mechanism.go diff --git a/vendor/github.com/containers/image/v4/signature/mechanism_gpgme.go b/vendor/github.com/containers/image/v5/signature/mechanism_gpgme.go similarity index 100% rename from vendor/github.com/containers/image/v4/signature/mechanism_gpgme.go rename to vendor/github.com/containers/image/v5/signature/mechanism_gpgme.go diff --git a/vendor/github.com/containers/image/v4/signature/mechanism_openpgp.go b/vendor/github.com/containers/image/v5/signature/mechanism_openpgp.go similarity index 100% rename from vendor/github.com/containers/image/v4/signature/mechanism_openpgp.go rename to vendor/github.com/containers/image/v5/signature/mechanism_openpgp.go diff --git a/vendor/github.com/containers/image/v4/signature/policy_config.go b/vendor/github.com/containers/image/v5/signature/policy_config.go similarity index 99% rename from vendor/github.com/containers/image/v4/signature/policy_config.go rename to vendor/github.com/containers/image/v5/signature/policy_config.go index bb229f5f1a7..3eee70bc2aa 100644 --- a/vendor/github.com/containers/image/v4/signature/policy_config.go +++ b/vendor/github.com/containers/image/v5/signature/policy_config.go @@ -19,9 +19,9 @@ import ( "io/ioutil" "path/filepath" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containers/image/v4/signature/policy_eval.go b/vendor/github.com/containers/image/v5/signature/policy_eval.go similarity index 99% rename from vendor/github.com/containers/image/v4/signature/policy_eval.go rename to vendor/github.com/containers/image/v5/signature/policy_eval.go index 110d40f7c9a..e94de2a9cae 100644 --- a/vendor/github.com/containers/image/v4/signature/policy_eval.go +++ b/vendor/github.com/containers/image/v5/signature/policy_eval.go @@ -8,7 +8,7 @@ package signature import ( "context" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) diff --git a/vendor/github.com/containers/image/v4/signature/policy_eval_baselayer.go b/vendor/github.com/containers/image/v5/signature/policy_eval_baselayer.go similarity index 93% rename from vendor/github.com/containers/image/v4/signature/policy_eval_baselayer.go rename to vendor/github.com/containers/image/v5/signature/policy_eval_baselayer.go index 18fb651d12b..55cdd3054f1 100644 --- a/vendor/github.com/containers/image/v4/signature/policy_eval_baselayer.go +++ b/vendor/github.com/containers/image/v5/signature/policy_eval_baselayer.go @@ -5,7 +5,7 @@ package signature import ( "context" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" "github.com/sirupsen/logrus" ) diff --git a/vendor/github.com/containers/image/v4/signature/policy_eval_signedby.go b/vendor/github.com/containers/image/v5/signature/policy_eval_signedby.go similarity index 96% rename from vendor/github.com/containers/image/v4/signature/policy_eval_signedby.go rename to vendor/github.com/containers/image/v5/signature/policy_eval_signedby.go index b8188da5e25..26cca4759e0 100644 --- a/vendor/github.com/containers/image/v4/signature/policy_eval_signedby.go +++ b/vendor/github.com/containers/image/v5/signature/policy_eval_signedby.go @@ -8,11 +8,10 @@ import ( "io/ioutil" "strings" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" + digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" - - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/types" - "github.com/opencontainers/go-digest" ) func (pr *prSignedBy) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) { diff --git a/vendor/github.com/containers/image/v4/signature/policy_eval_simple.go b/vendor/github.com/containers/image/v5/signature/policy_eval_simple.go similarity index 93% rename from vendor/github.com/containers/image/v4/signature/policy_eval_simple.go rename to vendor/github.com/containers/image/v5/signature/policy_eval_simple.go index 7fbcf4a946f..f949088b5f6 100644 --- a/vendor/github.com/containers/image/v4/signature/policy_eval_simple.go +++ b/vendor/github.com/containers/image/v5/signature/policy_eval_simple.go @@ -6,8 +6,8 @@ import ( "context" "fmt" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" ) func (pr *prInsecureAcceptAnything) isSignatureAuthorAccepted(ctx context.Context, image types.UnparsedImage, sig []byte) (signatureAcceptanceResult, *Signature, error) { diff --git a/vendor/github.com/containers/image/v4/signature/policy_reference_match.go b/vendor/github.com/containers/image/v5/signature/policy_reference_match.go similarity index 96% rename from vendor/github.com/containers/image/v4/signature/policy_reference_match.go rename to vendor/github.com/containers/image/v5/signature/policy_reference_match.go index 016d737fbae..a148ede5239 100644 --- a/vendor/github.com/containers/image/v4/signature/policy_reference_match.go +++ b/vendor/github.com/containers/image/v5/signature/policy_reference_match.go @@ -5,9 +5,9 @@ package signature import ( "fmt" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" ) // parseImageAndDockerReference converts an image and a reference string into two parsed entities, failing on any error and handling unidentified images. diff --git a/vendor/github.com/containers/image/v4/signature/policy_types.go b/vendor/github.com/containers/image/v5/signature/policy_types.go similarity index 100% rename from vendor/github.com/containers/image/v4/signature/policy_types.go rename to vendor/github.com/containers/image/v5/signature/policy_types.go diff --git a/vendor/github.com/containers/image/v4/signature/signature.go b/vendor/github.com/containers/image/v5/signature/signature.go similarity index 99% rename from vendor/github.com/containers/image/v4/signature/signature.go rename to vendor/github.com/containers/image/v5/signature/signature.go index 09c4de0b363..44e70b3b963 100644 --- a/vendor/github.com/containers/image/v4/signature/signature.go +++ b/vendor/github.com/containers/image/v5/signature/signature.go @@ -9,10 +9,9 @@ import ( "fmt" "time" + "github.com/containers/image/v5/version" + digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" - - "github.com/containers/image/v4/version" - "github.com/opencontainers/go-digest" ) const ( diff --git a/vendor/github.com/containers/image/v4/storage/storage_image.go b/vendor/github.com/containers/image/v5/storage/storage_image.go similarity index 80% rename from vendor/github.com/containers/image/v4/storage/storage_image.go rename to vendor/github.com/containers/image/v5/storage/storage_image.go index 4e913b84c0a..2b89f329f5f 100644 --- a/vendor/github.com/containers/image/v4/storage/storage_image.go +++ b/vendor/github.com/containers/image/v5/storage/storage_image.go @@ -6,6 +6,7 @@ import ( "bytes" "context" "encoding/json" + stderrors "errors" "fmt" "io" "io/ioutil" @@ -14,12 +15,12 @@ import ( "sync" "sync/atomic" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/image" - "github.com/containers/image/v4/internal/tmpdir" - "github.com/containers/image/v4/manifest" - "github.com/containers/image/v4/pkg/blobinfocache/none" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/internal/tmpdir" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/pkg/blobinfocache/none" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/ioutils" @@ -32,38 +33,38 @@ import ( var ( // ErrBlobDigestMismatch is returned when PutBlob() is given a blob // with a digest-based name that doesn't match its contents. - ErrBlobDigestMismatch = errors.New("blob digest mismatch") + ErrBlobDigestMismatch = stderrors.New("blob digest mismatch") // ErrBlobSizeMismatch is returned when PutBlob() is given a blob // with an expected size that doesn't match the reader. - ErrBlobSizeMismatch = errors.New("blob size mismatch") - // ErrNoManifestLists is returned when GetManifest() is called. - // with a non-nil instanceDigest. - ErrNoManifestLists = errors.New("manifest lists are not supported by this transport") + ErrBlobSizeMismatch = stderrors.New("blob size mismatch") // ErrNoSuchImage is returned when we attempt to access an image which // doesn't exist in the storage area. ErrNoSuchImage = storage.ErrNotAnImage ) type storageImageSource struct { - imageRef storageReference - image *storage.Image - layerPosition map[digest.Digest]int // Where we are in reading a blob's layers - cachedManifest []byte // A cached copy of the manifest, if already known, or nil - getBlobMutex sync.Mutex // Mutex to sync state for parallel GetBlob executions - SignatureSizes []int `json:"signature-sizes,omitempty"` // List of sizes of each signature slice + imageRef storageReference + image *storage.Image + layerPosition map[digest.Digest]int // Where we are in reading a blob's layers + cachedManifest []byte // A cached copy of the manifest, if already known, or nil + getBlobMutex sync.Mutex // Mutex to sync state for parallel GetBlob executions + SignatureSizes []int `json:"signature-sizes,omitempty"` // List of sizes of each signature slice + SignaturesSizes map[digest.Digest][]int `json:"signatures-sizes,omitempty"` // List of sizes of each signature slice } type storageImageDestination struct { - imageRef storageReference - directory string // Temporary directory where we store blobs until Commit() time - nextTempFileID int32 // A counter that we use for computing filenames to assign to blobs - manifest []byte // Manifest contents, temporary - signatures []byte // Signature contents, temporary - putBlobMutex sync.Mutex // Mutex to sync state for parallel PutBlob executions - blobDiffIDs map[digest.Digest]digest.Digest // Mapping from layer blobsums to their corresponding DiffIDs - fileSizes map[digest.Digest]int64 // Mapping from layer blobsums to their sizes - filenames map[digest.Digest]string // Mapping from layer blobsums to names of files we used to hold them - SignatureSizes []int `json:"signature-sizes,omitempty"` // List of sizes of each signature slice + imageRef storageReference + directory string // Temporary directory where we store blobs until Commit() time + nextTempFileID int32 // A counter that we use for computing filenames to assign to blobs + manifest []byte // Manifest contents, temporary + signatures []byte // Signature contents, temporary + signatureses map[digest.Digest][]byte // Instance signature contents, temporary + putBlobMutex sync.Mutex // Mutex to sync state for parallel PutBlob executions + blobDiffIDs map[digest.Digest]digest.Digest // Mapping from layer blobsums to their corresponding DiffIDs + fileSizes map[digest.Digest]int64 // Mapping from layer blobsums to their sizes + filenames map[digest.Digest]string // Mapping from layer blobsums to names of files we used to hold them + SignatureSizes []int `json:"signature-sizes,omitempty"` // List of sizes of each signature slice + SignaturesSizes map[digest.Digest][]int `json:"signatures-sizes,omitempty"` // Sizes of each manifest's signature slice } type storageImageCloser struct { @@ -72,26 +73,33 @@ type storageImageCloser struct { } // manifestBigDataKey returns a key suitable for recording a manifest with the specified digest using storage.Store.ImageBigData and related functions. -// If a specific manifest digest is explicitly requested by the user, the key retruned function should be used preferably; +// If a specific manifest digest is explicitly requested by the user, the key returned by this function should be used preferably; // for compatibility, if a manifest is not available under this key, check also storage.ImageDigestBigDataKey func manifestBigDataKey(digest digest.Digest) string { return storage.ImageDigestManifestBigDataNamePrefix + "-" + digest.String() } +// signatureBigDataKey returns a key suitable for recording the signatures associated with the manifest with the specified digest using storage.Store.ImageBigData and related functions. +// If a specific manifest digest is explicitly requested by the user, the key returned by this function should be used preferably; +func signatureBigDataKey(digest digest.Digest) string { + return "signature-" + digest.Encoded() +} + // newImageSource sets up an image for reading. -func newImageSource(imageRef storageReference) (*storageImageSource, error) { +func newImageSource(ctx context.Context, sys *types.SystemContext, imageRef storageReference) (*storageImageSource, error) { // First, locate the image. - img, err := imageRef.resolveImage() + img, err := imageRef.resolveImage(sys) if err != nil { return nil, err } // Build the reader object. image := &storageImageSource{ - imageRef: imageRef, - image: img, - layerPosition: make(map[digest.Digest]int), - SignatureSizes: []int{}, + imageRef: imageRef, + image: img, + layerPosition: make(map[digest.Digest]int), + SignatureSizes: []int{}, + SignaturesSizes: make(map[digest.Digest][]int), } if img.Metadata != "" { if err := json.Unmarshal([]byte(img.Metadata), image); err != nil { @@ -182,7 +190,12 @@ func (s *storageImageSource) getBlobAndLayerID(info types.BlobInfo) (rc io.ReadC // GetManifest() reads the image's manifest. func (s *storageImageSource) GetManifest(ctx context.Context, instanceDigest *digest.Digest) (manifestBlob []byte, MIMEType string, err error) { if instanceDigest != nil { - return nil, "", ErrNoManifestLists + key := manifestBigDataKey(*instanceDigest) + blob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, key) + if err != nil { + return nil, "", errors.Wrapf(err, "error reading manifest for image instance %q", *instanceDigest) + } + return blob, manifest.GuessMIMEType(blob), err } if len(s.cachedManifest) == 0 { // The manifest is stored as a big data item. @@ -214,11 +227,14 @@ func (s *storageImageSource) GetManifest(ctx context.Context, instanceDigest *di // LayerInfosForCopy() returns the list of layer blobs that make up the root filesystem of // the image, after they've been decompressed. -func (s *storageImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { - manifestBlob, manifestType, err := s.GetManifest(ctx, nil) +func (s *storageImageSource) LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]types.BlobInfo, error) { + manifestBlob, manifestType, err := s.GetManifest(ctx, instanceDigest) if err != nil { return nil, errors.Wrapf(err, "error reading image manifest for %q", s.image.ID) } + if manifest.MIMETypeIsMultiImage(manifestType) { + return nil, errors.Errorf("can't copy layers for a manifest list (shouldn't be attempted)") + } man, err := manifest.FromBlob(manifestBlob, manifestType) if err != nil { return nil, errors.Wrapf(err, "error parsing image manifest for %q", s.image.ID) @@ -292,25 +308,33 @@ func buildLayerInfosForCopy(manifestInfos []manifest.LayerInfo, physicalInfos [] // GetSignatures() parses the image's signatures blob into a slice of byte slices. func (s *storageImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) (signatures [][]byte, err error) { - if instanceDigest != nil { - return nil, ErrNoManifestLists - } var offset int sigslice := [][]byte{} signature := []byte{} - if len(s.SignatureSizes) > 0 { - signatureBlob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, "signatures") + signatureSizes := s.SignatureSizes + key := "signatures" + instance := "default instance" + if instanceDigest != nil { + signatureSizes = s.SignaturesSizes[*instanceDigest] + key = signatureBigDataKey(*instanceDigest) + instance = instanceDigest.Encoded() + } + if len(signatureSizes) > 0 { + signatureBlob, err := s.imageRef.transport.store.ImageBigData(s.image.ID, key) if err != nil { - return nil, errors.Wrapf(err, "error looking up signatures data for image %q", s.image.ID) + return nil, errors.Wrapf(err, "error looking up signatures data for image %q (%s)", s.image.ID, instance) } signature = signatureBlob } - for _, length := range s.SignatureSizes { + for _, length := range signatureSizes { + if offset+length > len(signature) { + return nil, errors.Wrapf(err, "error looking up signatures data for image %q (%s): expected at least %d bytes, only found %d", s.image.ID, instance, len(signature), offset+length) + } sigslice = append(sigslice, signature[offset:offset+length]) offset += length } if offset != len(signature) { - return nil, errors.Errorf("signatures data contained %d extra bytes", len(signatures)-offset) + return nil, errors.Errorf("signatures data (%s) contained %d extra bytes", instance, len(signatures)-offset) } return sigslice, nil } @@ -323,12 +347,14 @@ func newImageDestination(imageRef storageReference) (*storageImageDestination, e return nil, errors.Wrapf(err, "error creating a temporary directory") } image := &storageImageDestination{ - imageRef: imageRef, - directory: directory, - blobDiffIDs: make(map[digest.Digest]digest.Digest), - fileSizes: make(map[digest.Digest]int64), - filenames: make(map[digest.Digest]string), - SignatureSizes: []int{}, + imageRef: imageRef, + directory: directory, + signatureses: make(map[digest.Digest][]byte), + blobDiffIDs: make(map[digest.Digest]digest.Digest), + fileSizes: make(map[digest.Digest]int64), + filenames: make(map[digest.Digest]string), + SignatureSizes: []int{}, + SignaturesSizes: make(map[digest.Digest][]int), } return image, nil } @@ -404,10 +430,10 @@ func (s *storageImageDestination) PutBlob(ctx context.Context, stream io.Reader, } // Ensure that any information that we were given about the blob is correct. if blobinfo.Digest.Validate() == nil && blobinfo.Digest != hasher.Digest() { - return errorBlobInfo, ErrBlobDigestMismatch + return errorBlobInfo, errors.WithStack(ErrBlobDigestMismatch) } if blobinfo.Size >= 0 && blobinfo.Size != counter.Count { - return errorBlobInfo, ErrBlobSizeMismatch + return errorBlobInfo, errors.WithStack(ErrBlobSizeMismatch) } // Record information about the blob. s.putBlobMutex.Lock() @@ -579,7 +605,34 @@ func (s *storageImageDestination) getConfigBlob(info types.BlobInfo) ([]byte, er return nil, errors.New("blob not found") } -func (s *storageImageDestination) Commit(ctx context.Context) error { +func (s *storageImageDestination) Commit(ctx context.Context, unparsedToplevel types.UnparsedImage) error { + if len(s.manifest) == 0 { + return errors.New("Internal error: storageImageDestination.Commit() called without PutManifest()") + } + toplevelManifest, _, err := unparsedToplevel.Manifest(ctx) + if err != nil { + return errors.Wrapf(err, "error retrieving top-level manifest") + } + // If the name we're saving to includes a digest, then check that the + // manifests that we're about to save all either match the one from the + // unparsedToplevel, or match the digest in the name that we're using. + if s.imageRef.named != nil { + if digested, ok := s.imageRef.named.(reference.Digested); ok { + matches, err := manifest.MatchesDigest(s.manifest, digested.Digest()) + if err != nil { + return err + } + if !matches { + matches, err = manifest.MatchesDigest(toplevelManifest, digested.Digest()) + if err != nil { + return err + } + } + if !matches { + return fmt.Errorf("Manifest to be saved does not match expected digest %s", digested.Digest()) + } + } + } // Find the list of layer blobs. if len(s.manifest) == 0 { return errors.New("Internal error: storageImageDestination.Commit() called without PutManifest()") @@ -747,7 +800,8 @@ func (s *storageImageDestination) Commit(ctx context.Context) error { return errors.Wrapf(err, "error saving big data %q for image %q", blob.String(), img.ID) } } - // Set the reference's name on the image. + // Set the reference's name on the image. We don't need to worry about avoiding duplicate + // values because SetNames() will deduplicate the list that we pass to it. if name := s.imageRef.DockerReference(); len(oldNames) > 0 || name != nil { names := []string{} if name != nil { @@ -765,26 +819,43 @@ func (s *storageImageDestination) Commit(ctx context.Context) error { } logrus.Debugf("set names of image %q to %v", img.ID, names) } - // Save the manifest. Allow looking it up by digest by using the key convention defined by the Store. + // Save the unparsedToplevel's manifest. + if len(toplevelManifest) != 0 { + manifestDigest, err := manifest.Digest(toplevelManifest) + if err != nil { + return errors.Wrapf(err, "error digesting top-level manifest") + } + key := manifestBigDataKey(manifestDigest) + if err := s.imageRef.transport.store.SetImageBigData(img.ID, key, toplevelManifest, manifest.Digest); err != nil { + if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil { + logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2) + } + logrus.Debugf("error saving top-level manifest for image %q: %v", img.ID, err) + return errors.Wrapf(err, "error saving top-level manifest for image %q", img.ID) + } + } + // Save the image's manifest. Allow looking it up by digest by using the key convention defined by the Store. // Record the manifest twice: using a digest-specific key to allow references to that specific digest instance, // and using storage.ImageDigestBigDataKey for future users that don’t specify any digest and for compatibility with older readers. manifestDigest, err := manifest.Digest(s.manifest) if err != nil { return errors.Wrapf(err, "error computing manifest digest") } - if err := s.imageRef.transport.store.SetImageBigData(img.ID, manifestBigDataKey(manifestDigest), s.manifest, manifest.Digest); err != nil { + key := manifestBigDataKey(manifestDigest) + if err := s.imageRef.transport.store.SetImageBigData(img.ID, key, s.manifest, manifest.Digest); err != nil { if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil { logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2) } logrus.Debugf("error saving manifest for image %q: %v", img.ID, err) - return err + return errors.Wrapf(err, "error saving manifest for image %q", img.ID) } - if err := s.imageRef.transport.store.SetImageBigData(img.ID, storage.ImageDigestBigDataKey, s.manifest, manifest.Digest); err != nil { + key = storage.ImageDigestBigDataKey + if err := s.imageRef.transport.store.SetImageBigData(img.ID, key, s.manifest, manifest.Digest); err != nil { if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil { logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2) } logrus.Debugf("error saving manifest for image %q: %v", img.ID, err) - return err + return errors.Wrapf(err, "error saving manifest for image %q", img.ID) } // Save the signatures, if we have any. if len(s.signatures) > 0 { @@ -793,7 +864,17 @@ func (s *storageImageDestination) Commit(ctx context.Context) error { logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2) } logrus.Debugf("error saving signatures for image %q: %v", img.ID, err) - return err + return errors.Wrapf(err, "error saving signatures for image %q", img.ID) + } + } + for instanceDigest, signatures := range s.signatureses { + key := signatureBigDataKey(instanceDigest) + if err := s.imageRef.transport.store.SetImageBigData(img.ID, key, signatures, manifest.Digest); err != nil { + if _, err2 := s.imageRef.transport.store.DeleteImage(img.ID, true); err2 != nil { + logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2) + } + logrus.Debugf("error saving signatures for image %q: %v", img.ID, err) + return errors.Wrapf(err, "error saving signatures for image %q", img.ID) } } // Save our metadata. @@ -803,7 +884,7 @@ func (s *storageImageDestination) Commit(ctx context.Context) error { logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2) } logrus.Debugf("error encoding metadata for image %q: %v", img.ID, err) - return err + return errors.Wrapf(err, "error encoding metadata for image %q", img.ID) } if len(metadata) != 0 { if err = s.imageRef.transport.store.SetMetadata(img.ID, string(metadata)); err != nil { @@ -811,7 +892,7 @@ func (s *storageImageDestination) Commit(ctx context.Context) error { logrus.Debugf("error deleting incomplete image %q: %v", img.ID, err2) } logrus.Debugf("error saving metadata for image %q: %v", img.ID, err) - return err + return errors.Wrapf(err, "error saving metadata for image %q", img.ID) } logrus.Debugf("saved image metadata %q", string(metadata)) } @@ -830,21 +911,10 @@ func (s *storageImageDestination) SupportedManifestMIMETypes() []string { } // PutManifest writes the manifest to the destination. -func (s *storageImageDestination) PutManifest(ctx context.Context, manifestBlob []byte) error { - if s.imageRef.named != nil { - if digested, ok := s.imageRef.named.(reference.Digested); ok { - matches, err := manifest.MatchesDigest(manifestBlob, digested.Digest()) - if err != nil { - return err - } - if !matches { - return fmt.Errorf("Manifest does not match expected digest %s", digested.Digest()) - } - } - } - - s.manifest = make([]byte, len(manifestBlob)) - copy(s.manifest, manifestBlob) +func (s *storageImageDestination) PutManifest(ctx context.Context, manifestBlob []byte, instanceDigest *digest.Digest) error { + newBlob := make([]byte, len(manifestBlob)) + copy(newBlob, manifestBlob) + s.manifest = newBlob return nil } @@ -873,7 +943,7 @@ func (s *storageImageDestination) IgnoresEmbeddedDockerReference() bool { } // PutSignatures records the image's signatures for committing as a single data blob. -func (s *storageImageDestination) PutSignatures(ctx context.Context, signatures [][]byte) error { +func (s *storageImageDestination) PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error { sizes := []int{} sigblob := []byte{} for _, sig := range signatures { @@ -883,8 +953,21 @@ func (s *storageImageDestination) PutSignatures(ctx context.Context, signatures copy(newblob[len(sigblob):], sig) sigblob = newblob } - s.signatures = sigblob - s.SignatureSizes = sizes + if instanceDigest == nil { + s.signatures = sigblob + s.SignatureSizes = sizes + } + if instanceDigest == nil && len(s.manifest) > 0 { + manifestDigest, err := manifest.Digest(s.manifest) + if err != nil { + return err + } + instanceDigest = &manifestDigest + } + if instanceDigest != nil { + s.signatureses[*instanceDigest] = sigblob + s.SignaturesSizes[*instanceDigest] = sizes + } return nil } @@ -940,7 +1023,7 @@ func (s *storageImageCloser) Size() (int64, error) { // newImage creates an image that also knows its size func newImage(ctx context.Context, sys *types.SystemContext, s storageReference) (types.ImageCloser, error) { - src, err := newImageSource(s) + src, err := newImageSource(ctx, sys, s) if err != nil { return nil, err } diff --git a/vendor/github.com/containers/image/v4/storage/storage_reference.go b/vendor/github.com/containers/image/v5/storage/storage_reference.go similarity index 69% rename from vendor/github.com/containers/image/v4/storage/storage_reference.go rename to vendor/github.com/containers/image/v5/storage/storage_reference.go index 7ad20817b86..4e137ad1b1c 100644 --- a/vendor/github.com/containers/image/v4/storage/storage_reference.go +++ b/vendor/github.com/containers/image/v5/storage/storage_reference.go @@ -6,9 +6,12 @@ import ( "context" "strings" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/manifest" + "github.com/containers/image/v5/types" "github.com/containers/storage" + digest "github.com/opencontainers/go-digest" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -51,9 +54,79 @@ func imageMatchesRepo(image *storage.Image, ref reference.Named) bool { return false } +// imageMatchesSystemContext checks if the passed-in image both contains a +// manifest that matches the passed-in digest, and identifies itself as being +// appropriate for running on the system that matches sys. +// If we somehow ended up sharing the same storage among multiple types of +// systems, and managed to download multiple images from the same manifest +// list, their image records will all contain copies of the manifest list, and +// this check will help us decide which of them we want to return when we've +// been asked to resolve an image reference that uses the list's digest to a +// specific image ID. +func imageMatchesSystemContext(store storage.Store, img *storage.Image, manifestDigest digest.Digest, sys *types.SystemContext) bool { + // First, check if the image record has a manifest that matches the + // specified digest. + key := manifestBigDataKey(manifestDigest) + manifestBytes, err := store.ImageBigData(img.ID, key) + if err != nil { + return false + } + // The manifest is either a list, or not a list. If it's a list, find + // the digest of the instance that matches the current system, and try + // to load that manifest from the image record, and use it. + manifestType := manifest.GuessMIMEType(manifestBytes) + if manifest.MIMETypeIsMultiImage(manifestType) { + list, err := manifest.ListFromBlob(manifestBytes, manifestType) + if err != nil { + return false + } + manifestDigest, err = list.ChooseInstance(sys) + if err != nil { + return false + } + key = manifestBigDataKey(manifestDigest) + manifestBytes, err = store.ImageBigData(img.ID, key) + if err != nil { + return false + } + manifestType = manifest.GuessMIMEType(manifestBytes) + } + // Load the image's configuration blob. + m, err := manifest.FromBlob(manifestBytes, manifestType) + getConfig := func(blobInfo types.BlobInfo) ([]byte, error) { + return store.ImageBigData(img.ID, blobInfo.Digest.String()) + } + ii, err := m.Inspect(getConfig) + if err != nil { + return false + } + // Build a dummy index containing one instance and information about + // the image's target system from the image's configuration. + index := manifest.OCI1IndexFromComponents([]imgspecv1.Descriptor{{ + MediaType: imgspecv1.MediaTypeImageManifest, + Digest: manifestDigest, + Size: int64(len(manifestBytes)), + Platform: &imgspecv1.Platform{ + OS: ii.Os, + Architecture: ii.Architecture, + }, + }}, nil) + // Check that ChooseInstance() would select this image for this system, + // from a list of images. + instanceDigest, err := index.ChooseInstance(sys) + if err != nil { + return false + } + // Double-check that we can read the runnable image's manifest from the + // image record. + key = manifestBigDataKey(instanceDigest) + _, err = store.ImageBigData(img.ID, key) + return err == nil +} + // Resolve the reference's name to an image ID in the store, if there's already // one present with the same name or ID, and return the image. -func (s *storageReference) resolveImage() (*storage.Image, error) { +func (s *storageReference) resolveImage(sys *types.SystemContext) (*storage.Image, error) { var loadedImage *storage.Image if s.id == "" && s.named != nil { // Look for an image that has the expanded reference name as an explicit Name value. @@ -72,9 +145,10 @@ func (s *storageReference) resolveImage() (*storage.Image, error) { if err == nil && len(images) > 0 { for _, image := range images { if imageMatchesRepo(image, s.named) { - loadedImage = image - s.id = image.ID - break + if loadedImage == nil || imageMatchesSystemContext(s.transport.store, image, digested.Digest(), sys) { + loadedImage = image + s.id = image.ID + } } } } @@ -202,7 +276,7 @@ func (s storageReference) NewImage(ctx context.Context, sys *types.SystemContext } func (s storageReference) DeleteImage(ctx context.Context, sys *types.SystemContext) error { - img, err := s.resolveImage() + img, err := s.resolveImage(sys) if err != nil { return err } @@ -217,7 +291,7 @@ func (s storageReference) DeleteImage(ctx context.Context, sys *types.SystemCont } func (s storageReference) NewImageSource(ctx context.Context, sys *types.SystemContext) (types.ImageSource, error) { - return newImageSource(s) + return newImageSource(ctx, sys, s) } func (s storageReference) NewImageDestination(ctx context.Context, sys *types.SystemContext) (types.ImageDestination, error) { diff --git a/vendor/github.com/containers/image/v4/storage/storage_transport.go b/vendor/github.com/containers/image/v5/storage/storage_transport.go similarity index 98% rename from vendor/github.com/containers/image/v4/storage/storage_transport.go rename to vendor/github.com/containers/image/v5/storage/storage_transport.go index 48b909c0359..62a091da4c4 100644 --- a/vendor/github.com/containers/image/v4/storage/storage_transport.go +++ b/vendor/github.com/containers/image/v5/storage/storage_transport.go @@ -7,14 +7,13 @@ import ( "path/filepath" "strings" - "github.com/pkg/errors" - - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" digest "github.com/opencontainers/go-digest" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -288,7 +287,7 @@ func (s storageTransport) GetStoreImage(store storage.Store, ref types.ImageRefe } if sref, ok := ref.(*storageReference); ok { tmpRef := *sref - if img, err := tmpRef.resolveImage(); err == nil { + if img, err := tmpRef.resolveImage(&types.SystemContext{}); err == nil { return img, nil } } diff --git a/vendor/github.com/containers/image/v4/tarball/doc.go b/vendor/github.com/containers/image/v5/tarball/doc.go similarity index 84% rename from vendor/github.com/containers/image/v4/tarball/doc.go rename to vendor/github.com/containers/image/v5/tarball/doc.go index ebbe156bd40..ead2d4263d6 100644 --- a/vendor/github.com/containers/image/v4/tarball/doc.go +++ b/vendor/github.com/containers/image/v5/tarball/doc.go @@ -7,11 +7,10 @@ // import ( // "fmt" // -// cp "github.com/containers/image/v4/copy" -// "github.com/containers/image/v4/tarball" -// "github.com/containers/image/v4/transports/alltransports" -// -// imgspecv1 "github.com/containers/image/v4/transports/alltransports" +// cp "github.com/containers/image/v5/copy" +// "github.com/containers/image/v5/tarball" +// "github.com/containers/image/v5/transports/alltransports" +// imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" // ) // // func imageFromTarball() { diff --git a/vendor/github.com/containers/image/v4/tarball/tarball_reference.go b/vendor/github.com/containers/image/v5/tarball/tarball_reference.go similarity index 95% rename from vendor/github.com/containers/image/v4/tarball/tarball_reference.go rename to vendor/github.com/containers/image/v5/tarball/tarball_reference.go index d33c20de1b4..00150c53bbe 100644 --- a/vendor/github.com/containers/image/v4/tarball/tarball_reference.go +++ b/vendor/github.com/containers/image/v5/tarball/tarball_reference.go @@ -6,9 +6,9 @@ import ( "os" "strings" - "github.com/containers/image/v4/docker/reference" - "github.com/containers/image/v4/image" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/docker/reference" + "github.com/containers/image/v5/image" + "github.com/containers/image/v5/types" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" ) diff --git a/vendor/github.com/containers/image/v4/tarball/tarball_src.go b/vendor/github.com/containers/image/v5/tarball/tarball_src.go similarity index 91% rename from vendor/github.com/containers/image/v4/tarball/tarball_src.go rename to vendor/github.com/containers/image/v5/tarball/tarball_src.go index ead1a50bd36..694ad17bd1a 100644 --- a/vendor/github.com/containers/image/v4/tarball/tarball_src.go +++ b/vendor/github.com/containers/image/v5/tarball/tarball_src.go @@ -12,7 +12,7 @@ import ( "strings" "time" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" "github.com/klauspost/pgzip" digest "github.com/opencontainers/go-digest" imgspecs "github.com/opencontainers/image-spec/specs-go" @@ -248,9 +248,8 @@ func (is *tarballImageSource) GetManifest(ctx context.Context, instanceDigest *d } // GetSignatures returns the image's signatures. It may use a remote (= slow) service. -// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve signatures for -// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list -// (e.g. if the source never returns manifest lists). +// This source implementation does not support manifest lists, so the passed-in instanceDigest should always be nil, +// as there can be no secondary manifests. func (*tarballImageSource) GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) { if instanceDigest != nil { return nil, fmt.Errorf("manifest lists are not supported by the %q transport", transportName) @@ -262,7 +261,14 @@ func (is *tarballImageSource) Reference() types.ImageReference { return &is.reference } -// LayerInfosForCopy() returns updated layer info that should be used when reading, in preference to values in the manifest, if specified. -func (*tarballImageSource) LayerInfosForCopy(ctx context.Context) ([]types.BlobInfo, error) { +// LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer +// blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob() +// to read the image's layers. +// If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for +// (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list +// (e.g. if the source never returns manifest lists). +// The Digest field is guaranteed to be provided; Size may be -1. +// WARNING: The list may contain duplicates, and they are semantically relevant. +func (*tarballImageSource) LayerInfosForCopy(context.Context, *digest.Digest) ([]types.BlobInfo, error) { return nil, nil } diff --git a/vendor/github.com/containers/image/v4/tarball/tarball_transport.go b/vendor/github.com/containers/image/v5/tarball/tarball_transport.go similarity index 94% rename from vendor/github.com/containers/image/v4/tarball/tarball_transport.go rename to vendor/github.com/containers/image/v5/tarball/tarball_transport.go index 84874cfbfa8..113545cb794 100644 --- a/vendor/github.com/containers/image/v4/tarball/tarball_transport.go +++ b/vendor/github.com/containers/image/v5/tarball/tarball_transport.go @@ -7,8 +7,8 @@ import ( "os" "strings" - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" ) const ( diff --git a/vendor/github.com/containers/image/v4/transports/alltransports/alltransports.go b/vendor/github.com/containers/image/v5/transports/alltransports/alltransports.go similarity index 75% rename from vendor/github.com/containers/image/v4/transports/alltransports/alltransports.go rename to vendor/github.com/containers/image/v5/transports/alltransports/alltransports.go index ae68fb8e6e7..2110a091d28 100644 --- a/vendor/github.com/containers/image/v4/transports/alltransports/alltransports.go +++ b/vendor/github.com/containers/image/v5/transports/alltransports/alltransports.go @@ -6,17 +6,17 @@ import ( // register all known transports // NOTE: Make sure docs/containers-policy.json.5.md is updated when adding or updating // a transport. - _ "github.com/containers/image/v4/directory" - _ "github.com/containers/image/v4/docker" - _ "github.com/containers/image/v4/docker/archive" - _ "github.com/containers/image/v4/oci/archive" - _ "github.com/containers/image/v4/oci/layout" - _ "github.com/containers/image/v4/openshift" - _ "github.com/containers/image/v4/tarball" + _ "github.com/containers/image/v5/directory" + _ "github.com/containers/image/v5/docker" + _ "github.com/containers/image/v5/docker/archive" + _ "github.com/containers/image/v5/oci/archive" + _ "github.com/containers/image/v5/oci/layout" + _ "github.com/containers/image/v5/openshift" + _ "github.com/containers/image/v5/tarball" // The ostree transport is registered by ostree*.go // The storage transport is registered by storage*.go - "github.com/containers/image/v4/transports" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/transports" + "github.com/containers/image/v5/types" "github.com/pkg/errors" ) diff --git a/vendor/github.com/containers/image/v4/transports/alltransports/docker_daemon.go b/vendor/github.com/containers/image/v5/transports/alltransports/docker_daemon.go similarity index 71% rename from vendor/github.com/containers/image/v4/transports/alltransports/docker_daemon.go rename to vendor/github.com/containers/image/v5/transports/alltransports/docker_daemon.go index d3fc18b2c4f..82224052e66 100644 --- a/vendor/github.com/containers/image/v4/transports/alltransports/docker_daemon.go +++ b/vendor/github.com/containers/image/v5/transports/alltransports/docker_daemon.go @@ -4,5 +4,5 @@ package alltransports import ( // Register the docker-daemon transport - _ "github.com/containers/image/v4/docker/daemon" + _ "github.com/containers/image/v5/docker/daemon" ) diff --git a/vendor/github.com/containers/image/v4/transports/alltransports/docker_daemon_stub.go b/vendor/github.com/containers/image/v5/transports/alltransports/docker_daemon_stub.go similarity index 75% rename from vendor/github.com/containers/image/v4/transports/alltransports/docker_daemon_stub.go rename to vendor/github.com/containers/image/v5/transports/alltransports/docker_daemon_stub.go index 82e05537749..d137007991f 100644 --- a/vendor/github.com/containers/image/v4/transports/alltransports/docker_daemon_stub.go +++ b/vendor/github.com/containers/image/v5/transports/alltransports/docker_daemon_stub.go @@ -2,7 +2,7 @@ package alltransports -import "github.com/containers/image/v4/transports" +import "github.com/containers/image/v5/transports" func init() { transports.Register(transports.NewStubTransport("docker-daemon")) diff --git a/vendor/github.com/containers/image/v4/transports/alltransports/ostree.go b/vendor/github.com/containers/image/v5/transports/alltransports/ostree.go similarity index 71% rename from vendor/github.com/containers/image/v4/transports/alltransports/ostree.go rename to vendor/github.com/containers/image/v5/transports/alltransports/ostree.go index 89169661677..72432d1ef80 100644 --- a/vendor/github.com/containers/image/v4/transports/alltransports/ostree.go +++ b/vendor/github.com/containers/image/v5/transports/alltransports/ostree.go @@ -4,5 +4,5 @@ package alltransports import ( // Register the ostree transport - _ "github.com/containers/image/v4/ostree" + _ "github.com/containers/image/v5/ostree" ) diff --git a/vendor/github.com/containers/image/v4/transports/alltransports/ostree_stub.go b/vendor/github.com/containers/image/v5/transports/alltransports/ostree_stub.go similarity index 73% rename from vendor/github.com/containers/image/v4/transports/alltransports/ostree_stub.go rename to vendor/github.com/containers/image/v5/transports/alltransports/ostree_stub.go index 892518d5cf5..f4a862bd4e4 100644 --- a/vendor/github.com/containers/image/v4/transports/alltransports/ostree_stub.go +++ b/vendor/github.com/containers/image/v5/transports/alltransports/ostree_stub.go @@ -2,7 +2,7 @@ package alltransports -import "github.com/containers/image/v4/transports" +import "github.com/containers/image/v5/transports" func init() { transports.Register(transports.NewStubTransport("ostree")) diff --git a/vendor/github.com/containers/image/v4/transports/alltransports/storage.go b/vendor/github.com/containers/image/v5/transports/alltransports/storage.go similarity index 71% rename from vendor/github.com/containers/image/v4/transports/alltransports/storage.go rename to vendor/github.com/containers/image/v5/transports/alltransports/storage.go index 96f3e17fc90..7041eb876af 100644 --- a/vendor/github.com/containers/image/v4/transports/alltransports/storage.go +++ b/vendor/github.com/containers/image/v5/transports/alltransports/storage.go @@ -4,5 +4,5 @@ package alltransports import ( // Register the storage transport - _ "github.com/containers/image/v4/storage" + _ "github.com/containers/image/v5/storage" ) diff --git a/vendor/github.com/containers/image/v4/transports/alltransports/storage_stub.go b/vendor/github.com/containers/image/v5/transports/alltransports/storage_stub.go similarity index 75% rename from vendor/github.com/containers/image/v4/transports/alltransports/storage_stub.go rename to vendor/github.com/containers/image/v5/transports/alltransports/storage_stub.go index 14c94211646..67f0291cc08 100644 --- a/vendor/github.com/containers/image/v4/transports/alltransports/storage_stub.go +++ b/vendor/github.com/containers/image/v5/transports/alltransports/storage_stub.go @@ -2,7 +2,7 @@ package alltransports -import "github.com/containers/image/v4/transports" +import "github.com/containers/image/v5/transports" func init() { transports.Register(transports.NewStubTransport("containers-storage")) diff --git a/vendor/github.com/containers/image/v4/transports/stub.go b/vendor/github.com/containers/image/v5/transports/stub.go similarity index 97% rename from vendor/github.com/containers/image/v4/transports/stub.go rename to vendor/github.com/containers/image/v5/transports/stub.go index e3ee6203172..2c186a90cca 100644 --- a/vendor/github.com/containers/image/v4/transports/stub.go +++ b/vendor/github.com/containers/image/v5/transports/stub.go @@ -3,7 +3,7 @@ package transports import ( "fmt" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" ) // stubTransport is an implementation of types.ImageTransport which has a name, but rejects any references with “the transport $name: is not supported in this build”. diff --git a/vendor/github.com/containers/image/v4/transports/transports.go b/vendor/github.com/containers/image/v5/transports/transports.go similarity index 98% rename from vendor/github.com/containers/image/v4/transports/transports.go rename to vendor/github.com/containers/image/v5/transports/transports.go index 8bdb46b4b80..46ee3710fcb 100644 --- a/vendor/github.com/containers/image/v4/transports/transports.go +++ b/vendor/github.com/containers/image/v5/transports/transports.go @@ -5,7 +5,7 @@ import ( "sort" "sync" - "github.com/containers/image/v4/types" + "github.com/containers/image/v5/types" ) // knownTransports is a registry of known ImageTransport instances. diff --git a/vendor/github.com/containers/image/v4/types/types.go b/vendor/github.com/containers/image/v5/types/types.go similarity index 93% rename from vendor/github.com/containers/image/v4/types/types.go rename to vendor/github.com/containers/image/v5/types/types.go index af11a2b2127..2db8c78273d 100644 --- a/vendor/github.com/containers/image/v4/types/types.go +++ b/vendor/github.com/containers/image/v5/types/types.go @@ -5,8 +5,8 @@ import ( "io" "time" - "github.com/containers/image/v4/docker/reference" - compression "github.com/containers/image/v4/pkg/compression/types" + "github.com/containers/image/v5/docker/reference" + compression "github.com/containers/image/v5/pkg/compression/types" digest "github.com/opencontainers/go-digest" v1 "github.com/opencontainers/image-spec/specs-go/v1" ) @@ -227,10 +227,15 @@ type ImageSource interface { // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list // (e.g. if the source never returns manifest lists). GetSignatures(ctx context.Context, instanceDigest *digest.Digest) ([][]byte, error) - // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer blobsums that are listed in the image's manifest. + // LayerInfosForCopy returns either nil (meaning the values in the manifest are fine), or updated values for the layer + // blobsums that are listed in the image's manifest. If values are returned, they should be used when using GetBlob() + // to read the image's layers. + // If instanceDigest is not nil, it contains a digest of the specific manifest instance to retrieve BlobInfos for + // (when the primary manifest is a manifest list); this never happens if the primary manifest is not a manifest list + // (e.g. if the source never returns manifest lists). // The Digest field is guaranteed to be provided; Size may be -1. // WARNING: The list may contain duplicates, and they are semantically relevant. - LayerInfosForCopy(ctx context.Context) ([]BlobInfo, error) + LayerInfosForCopy(ctx context.Context, instanceDigest *digest.Digest) ([]BlobInfo, error) } // ImageDestination is a service, possibly remote (= slow), to store components of a single image. @@ -286,16 +291,24 @@ type ImageDestination interface { // May use and/or update cache. TryReusingBlob(ctx context.Context, info BlobInfo, cache BlobInfoCache, canSubstitute bool) (bool, BlobInfo, error) // PutManifest writes manifest to the destination. + // If instanceDigest is not nil, it contains a digest of the specific manifest instance to write the manifest for + // (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list. + // It is expected but not enforced that the instanceDigest, when specified, matches the digest of `manifest` as generated + // by `manifest.Digest()`. // FIXME? This should also receive a MIME type if known, to differentiate between schema versions. // If the destination is in principle available, refuses this manifest type (e.g. it does not recognize the schema), // but may accept a different manifest type, the returned error must be an ManifestTypeRejectedError. - PutManifest(ctx context.Context, manifest []byte) error - PutSignatures(ctx context.Context, signatures [][]byte) error + PutManifest(ctx context.Context, manifest []byte, instanceDigest *digest.Digest) error + // PutSignatures writes a set of signatures to the destination. + // If instanceDigest is not nil, it contains a digest of the specific manifest instance to write or overwrite the signatures for + // (when the primary manifest is a manifest list); this should always be nil if the primary manifest is not a manifest list. + // MUST be called after PutManifest (signatures may reference manifest contents). + PutSignatures(ctx context.Context, signatures [][]byte, instanceDigest *digest.Digest) error // Commit marks the process of storing the image as successful and asks for the image to be persisted. // WARNING: This does not have any transactional semantics: // - Uploaded data MAY be visible to others before Commit() is called // - Uploaded data MAY be removed or MAY remain around if Close() is called without Commit() (i.e. rollback is allowed but not guaranteed) - Commit(ctx context.Context) error + Commit(ctx context.Context, unparsedToplevel UnparsedImage) error } // ManifestTypeRejectedError is returned by ImageDestination.PutManifest if the destination is in principle available, @@ -462,8 +475,15 @@ type SystemContext struct { RegistriesDirPath string // Path to the system-wide registries configuration file SystemRegistriesConfPath string - // If not "", overrides the default path for the authentication file + // If not "", overrides the default path for the authentication file, but only new format files AuthFilePath string + // if not "", overrides the default path for the authentication file, but with the legacy format; + // the code currently will by default look for legacy format files like .dockercfg in the $HOME dir; + // but in addition to the home dir, openshift may mount .dockercfg files (via secret mount) + // in locations other than the home dir; openshift components should then set this field in those cases; + // this field is ignored if `AuthFilePath` is set (we favor the newer format); + // only reading of this data is supported; + LegacyFormatAuthFilePath string // If not "", overrides the use of platform.GOARCH when choosing an image or verifying architecture match. ArchitectureChoice string // If not "", overrides the use of platform.GOOS when choosing an image or verifying OS match. diff --git a/vendor/github.com/containers/image/v4/version/version.go b/vendor/github.com/containers/image/v5/version/version.go similarity index 93% rename from vendor/github.com/containers/image/v4/version/version.go rename to vendor/github.com/containers/image/v5/version/version.go index 2fa6706df32..572be2b8908 100644 --- a/vendor/github.com/containers/image/v4/version/version.go +++ b/vendor/github.com/containers/image/v5/version/version.go @@ -4,11 +4,11 @@ import "fmt" const ( // VersionMajor is for an API incompatible changes - VersionMajor = 4 + VersionMajor = 5 // VersionMinor is for functionality in a backwards-compatible manner VersionMinor = 0 // VersionPatch is for backwards-compatible bug fixes - VersionPatch = 1 + VersionPatch = 0 // VersionDev indicates development branch. Releases will be empty string. VersionDev = "" diff --git a/vendor/modules.txt b/vendor/modules.txt index 177b68e369c..b53f99b5b49 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -44,44 +44,44 @@ github.com/containernetworking/cni/pkg/invoke github.com/containernetworking/cni/pkg/types github.com/containernetworking/cni/pkg/types/020 github.com/containernetworking/cni/pkg/types/current -# github.com/containers/image/v4 v4.0.1 -github.com/containers/image/v4/copy -github.com/containers/image/v4/directory -github.com/containers/image/v4/docker -github.com/containers/image/v4/docker/archive -github.com/containers/image/v4/docker/reference -github.com/containers/image/v4/docker/tarfile -github.com/containers/image/v4/image -github.com/containers/image/v4/manifest -github.com/containers/image/v4/oci/archive -github.com/containers/image/v4/oci/layout -github.com/containers/image/v4/pkg/sysregistriesv2 -github.com/containers/image/v4/signature -github.com/containers/image/v4/storage -github.com/containers/image/v4/transports -github.com/containers/image/v4/transports/alltransports -github.com/containers/image/v4/types -github.com/containers/image/v4/pkg/docker/config -github.com/containers/image/v4/pkg/strslice -github.com/containers/image/v4/pkg/blobinfocache -github.com/containers/image/v4/pkg/compression -github.com/containers/image/v4/directory/explicitfilepath -github.com/containers/image/v4/docker/policyconfiguration -github.com/containers/image/v4/pkg/blobinfocache/none -github.com/containers/image/v4/pkg/tlsclientconfig -github.com/containers/image/v4/internal/tmpdir -github.com/containers/image/v4/oci/internal -github.com/containers/image/v4/version -github.com/containers/image/v4/docker/daemon -github.com/containers/image/v4/openshift -github.com/containers/image/v4/ostree -github.com/containers/image/v4/tarball -github.com/containers/image/v4/pkg/compression/types -github.com/containers/image/v4/internal/pkg/keyctl -github.com/containers/image/v4/pkg/blobinfocache/boltdb -github.com/containers/image/v4/pkg/blobinfocache/memory -github.com/containers/image/v4/pkg/compression/internal -github.com/containers/image/v4/pkg/blobinfocache/internal/prioritize +# github.com/containers/image/v5 v5.0.0 +github.com/containers/image/v5/copy +github.com/containers/image/v5/directory +github.com/containers/image/v5/docker +github.com/containers/image/v5/docker/archive +github.com/containers/image/v5/docker/reference +github.com/containers/image/v5/docker/tarfile +github.com/containers/image/v5/image +github.com/containers/image/v5/manifest +github.com/containers/image/v5/oci/archive +github.com/containers/image/v5/oci/layout +github.com/containers/image/v5/pkg/sysregistriesv2 +github.com/containers/image/v5/signature +github.com/containers/image/v5/storage +github.com/containers/image/v5/transports +github.com/containers/image/v5/transports/alltransports +github.com/containers/image/v5/types +github.com/containers/image/v5/pkg/docker/config +github.com/containers/image/v5/version +github.com/containers/image/v5/pkg/strslice +github.com/containers/image/v5/pkg/blobinfocache +github.com/containers/image/v5/pkg/compression +github.com/containers/image/v5/directory/explicitfilepath +github.com/containers/image/v5/docker/policyconfiguration +github.com/containers/image/v5/pkg/blobinfocache/none +github.com/containers/image/v5/pkg/tlsclientconfig +github.com/containers/image/v5/internal/tmpdir +github.com/containers/image/v5/oci/internal +github.com/containers/image/v5/docker/daemon +github.com/containers/image/v5/openshift +github.com/containers/image/v5/ostree +github.com/containers/image/v5/tarball +github.com/containers/image/v5/pkg/compression/types +github.com/containers/image/v5/internal/pkg/keyctl +github.com/containers/image/v5/pkg/blobinfocache/boltdb +github.com/containers/image/v5/pkg/blobinfocache/memory +github.com/containers/image/v5/pkg/compression/internal +github.com/containers/image/v5/pkg/blobinfocache/internal/prioritize # github.com/containers/libtrust v0.0.0-20190913040956-14b96171aa3b github.com/containers/libtrust # github.com/containers/storage v1.13.5