From 98c26efd63c9cf979bd32bf066bcf62b41bf5ff0 Mon Sep 17 00:00:00 2001 From: Vishal Choudhary Date: Sun, 10 Sep 2023 13:05:54 +0530 Subject: [PATCH 1/8] feat: add platform flag to copy Signed-off-by: Vishal Choudhary --- cmd/cosign/cli/copy.go | 5 ++++- cmd/cosign/cli/options/copy.go | 4 ++++ doc/cosign_copy.md | 4 ++++ 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/cmd/cosign/cli/copy.go b/cmd/cosign/cli/copy.go index 7c23f7e3b70..593cb8fb572 100644 --- a/cmd/cosign/cli/copy.go +++ b/cmd/cosign/cli/copy.go @@ -37,7 +37,10 @@ func Copy() *cobra.Command { cosign copy --sig-only example.com/src example.com/dest # overwrite destination image and signatures - cosign copy -f example.com/src example.com/dest`, + cosign copy -f example.com/src example.com/dest + + # copy a container image and its signatures for a specific platform + cosign copy --platform=linux/amd64 example.com/src:latest example.com/dest:latest`, Args: cobra.ExactArgs(2), PersistentPreRun: options.BindViper, diff --git a/cmd/cosign/cli/options/copy.go b/cmd/cosign/cli/options/copy.go index e9c5c70134b..4e2a99125f9 100644 --- a/cmd/cosign/cli/options/copy.go +++ b/cmd/cosign/cli/options/copy.go @@ -23,6 +23,7 @@ import ( type CopyOptions struct { SignatureOnly bool Force bool + Platform string Registry RegistryOptions } @@ -37,4 +38,7 @@ func (o *CopyOptions) AddFlags(cmd *cobra.Command) { cmd.Flags().BoolVarP(&o.Force, "force", "f", false, "overwrite destination image(s), if necessary") + + cmd.Flags().StringVar(&o.Platform, "platform", "", + "only copy container image and its signatures for a specific platform image") } diff --git a/doc/cosign_copy.md b/doc/cosign_copy.md index 37a4fb0cdf1..331d292516e 100644 --- a/doc/cosign_copy.md +++ b/doc/cosign_copy.md @@ -19,6 +19,9 @@ cosign copy [flags] # overwrite destination image and signatures cosign copy -f example.com/src example.com/dest + + # copy a container image and its signatures for a specific platform + cosign copy --platform=linux/amd64 example.com/src:latest example.com/dest:latest ``` ### Options @@ -30,6 +33,7 @@ cosign copy [flags] -f, --force overwrite destination image(s), if necessary -h, --help help for copy --k8s-keychain whether to use the kubernetes keychain instead of the default keychain (supports workload identity). + --platform string only copy container image and its signatures for a specific platform image --sig-only only copy the image signature ``` From 965ff898f2544df1832855f87d8a06fc9e594550 Mon Sep 17 00:00:00 2001 From: Vishal Choudhary Date: Sun, 10 Sep 2023 13:06:35 +0530 Subject: [PATCH 2/8] feat: extract platformList to common package Signed-off-by: Vishal Choudhary --- cmd/cosign/cli/common/platform.go | 76 ++++++++++++++++++++++++ cmd/cosign/cli/download/attestation.go | 11 ++-- cmd/cosign/cli/download/sbom.go | 81 +++----------------------- 3 files changed, 89 insertions(+), 79 deletions(-) create mode 100644 cmd/cosign/cli/common/platform.go diff --git a/cmd/cosign/cli/common/platform.go b/cmd/cosign/cli/common/platform.go new file mode 100644 index 00000000000..4d59e5e0fa9 --- /dev/null +++ b/cmd/cosign/cli/common/platform.go @@ -0,0 +1,76 @@ +package common + +import ( + "fmt" + "strings" + + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/sigstore/cosign/v2/pkg/oci" +) + +type platformList []struct { + Hash v1.Hash + Platform *v1.Platform +} + +func (pl *platformList) String() string { + r := []string{} + for _, p := range *pl { + r = append(r, p.Platform.String()) + } + return strings.Join(r, ", ") +} + +func GetIndexPlatforms(idx oci.SignedImageIndex) (platformList, error) { + im, err := idx.IndexManifest() + if err != nil { + return nil, fmt.Errorf("fetching index manifest: %w", err) + } + + platforms := platformList{} + for _, m := range im.Manifests { + if m.Platform == nil { + continue + } + platforms = append(platforms, struct { + Hash v1.Hash + Platform *v1.Platform + }{m.Digest, m.Platform}) + } + return platforms, nil +} + +// matchPlatform filters a list of platforms returning only those matching +// a base. "Based" on ko's internal equivalent while it moves to GGCR. +// https://github.com/google/ko/blob/e6a7a37e26d82a8b2bb6df991c5a6cf6b2728794/pkg/build/gobuild.go#L1020 +func MatchPlatform(base *v1.Platform, list platformList) platformList { + ret := platformList{} + for _, p := range list { + if base.OS != "" && base.OS != p.Platform.OS { + continue + } + if base.Architecture != "" && base.Architecture != p.Platform.Architecture { + continue + } + if base.Variant != "" && base.Variant != p.Platform.Variant { + continue + } + + if base.OSVersion != "" && p.Platform.OSVersion != base.OSVersion { + if base.OS != "windows" { + continue + } else { //nolint: revive + if pcount, bcount := strings.Count(base.OSVersion, "."), strings.Count(p.Platform.OSVersion, "."); pcount == 2 && bcount == 3 { + if base.OSVersion != p.Platform.OSVersion[:strings.LastIndex(p.Platform.OSVersion, ".")] { + continue + } + } else { + continue + } + } + } + ret = append(ret, p) + } + + return ret +} diff --git a/cmd/cosign/cli/download/attestation.go b/cmd/cosign/cli/download/attestation.go index 92a2f6603d9..4784090f714 100644 --- a/cmd/cosign/cli/download/attestation.go +++ b/cmd/cosign/cli/download/attestation.go @@ -22,6 +22,7 @@ import ( "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/common" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/cosign/v2/pkg/oci" @@ -63,12 +64,12 @@ func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOpt if err != nil { return fmt.Errorf("parsing platform: %w", err) } - platforms, err := getIndexPlatforms(idx) + platforms, err := common.GetIndexPlatforms(idx) if err != nil { return fmt.Errorf("getting available platforms: %w", err) } - platforms = matchPlatform(targetPlatform, platforms) + platforms = common.MatchPlatform(targetPlatform, platforms) if len(platforms) == 0 { return fmt.Errorf("unable to find an attestation for %s", targetPlatform.String()) } @@ -79,12 +80,12 @@ func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOpt ) } - nse, err := idx.SignedImage(platforms[0].hash) + nse, err := idx.SignedImage(platforms[0].Hash) if err != nil { - return fmt.Errorf("searching for %s image: %w", platforms[0].hash.String(), err) + return fmt.Errorf("searching for %s image: %w", platforms[0].Hash.String(), err) } if nse == nil { - return fmt.Errorf("unable to find image %s", platforms[0].hash.String()) + return fmt.Errorf("unable to find image %s", platforms[0].Hash.String()) } se = nse } diff --git a/cmd/cosign/cli/download/sbom.go b/cmd/cosign/cli/download/sbom.go index 0d4816e4ca9..84bd300e21f 100644 --- a/cmd/cosign/cli/download/sbom.go +++ b/cmd/cosign/cli/download/sbom.go @@ -21,28 +21,15 @@ import ( "fmt" "io" "os" - "strings" "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/common" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/oci" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" ) -type platformList []struct { - hash v1.Hash - platform *v1.Platform -} - -func (pl *platformList) String() string { - r := []string{} - for _, p := range *pl { - r = append(r, p.platform.String()) - } - return strings.Join(r, ", ") -} - func SBOMCmd( ctx context.Context, regOpts options.RegistryOptions, dnOpts options.SBOMDownloadOptions, imageRef string, out io.Writer, @@ -74,12 +61,12 @@ func SBOMCmd( if err != nil { return nil, fmt.Errorf("parsing platform: %w", err) } - platforms, err := getIndexPlatforms(idx) + platforms, err := common.GetIndexPlatforms(idx) if err != nil { return nil, fmt.Errorf("getting available platforms: %w", err) } - platforms = matchPlatform(targetPlatform, platforms) + platforms = common.MatchPlatform(targetPlatform, platforms) if len(platforms) == 0 { return nil, fmt.Errorf("unable to find an SBOM for %s", targetPlatform.String()) } @@ -90,12 +77,12 @@ func SBOMCmd( ) } - nse, err := idx.SignedImage(platforms[0].hash) + nse, err := idx.SignedImage(platforms[0].Hash) if err != nil { - return nil, fmt.Errorf("searching for %s image: %w", platforms[0].hash.String(), err) + return nil, fmt.Errorf("searching for %s image: %w", platforms[0].Hash.String(), err) } if nse == nil { - return nil, fmt.Errorf("unable to find image %s", platforms[0].hash.String()) + return nil, fmt.Errorf("unable to find image %s", platforms[0].Hash.String()) } se = nse } @@ -106,7 +93,7 @@ func SBOMCmd( return nil, errors.New("no sbom attached to reference") } // Help the user with the available architectures - pl, err := getIndexPlatforms(idx) + pl, err := common.GetIndexPlatforms(idx) if len(pl) > 0 && err == nil { fmt.Fprintf( os.Stderr, @@ -139,57 +126,3 @@ func SBOMCmd( return sboms, nil } - -func getIndexPlatforms(idx oci.SignedImageIndex) (platformList, error) { - im, err := idx.IndexManifest() - if err != nil { - return nil, fmt.Errorf("fetching index manifest: %w", err) - } - - platforms := platformList{} - for _, m := range im.Manifests { - if m.Platform == nil { - continue - } - platforms = append(platforms, struct { - hash v1.Hash - platform *v1.Platform - }{m.Digest, m.Platform}) - } - return platforms, nil -} - -// matchPlatform filters a list of platforms returning only those matching -// a base. "Based" on ko's internal equivalent while it moves to GGCR. -// https://github.com/google/ko/blob/e6a7a37e26d82a8b2bb6df991c5a6cf6b2728794/pkg/build/gobuild.go#L1020 -func matchPlatform(base *v1.Platform, list platformList) platformList { - ret := platformList{} - for _, p := range list { - if base.OS != "" && base.OS != p.platform.OS { - continue - } - if base.Architecture != "" && base.Architecture != p.platform.Architecture { - continue - } - if base.Variant != "" && base.Variant != p.platform.Variant { - continue - } - - if base.OSVersion != "" && p.platform.OSVersion != base.OSVersion { - if base.OS != "windows" { - continue - } else { //nolint: revive - if pcount, bcount := strings.Count(base.OSVersion, "."), strings.Count(p.platform.OSVersion, "."); pcount == 2 && bcount == 3 { - if base.OSVersion != p.platform.OSVersion[:strings.LastIndex(p.platform.OSVersion, ".")] { - continue - } - } else { - continue - } - } - } - ret = append(ret, p) - } - - return ret -} From 1c993e3c146804197d344f86db973c5fcd736558 Mon Sep 17 00:00:00 2001 From: Vishal Choudhary Date: Sun, 10 Sep 2023 13:06:53 +0530 Subject: [PATCH 3/8] add platform check to copycmd Signed-off-by: Vishal Choudhary --- cmd/cosign/cli/copy.go | 2 +- cmd/cosign/cli/copy/copy.go | 40 +++++++++++++++++++++++++++++++- cmd/cosign/cli/copy/copy_test.go | 2 +- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/cmd/cosign/cli/copy.go b/cmd/cosign/cli/copy.go index 593cb8fb572..5af36483fd1 100644 --- a/cmd/cosign/cli/copy.go +++ b/cmd/cosign/cli/copy.go @@ -45,7 +45,7 @@ func Copy() *cobra.Command { Args: cobra.ExactArgs(2), PersistentPreRun: options.BindViper, RunE: func(cmd *cobra.Command, args []string) error { - return copy.CopyCmd(cmd.Context(), o.Registry, args[0], args[1], o.SignatureOnly, o.Force) + return copy.CopyCmd(cmd.Context(), o.Registry, args[0], args[1], o.SignatureOnly, o.Force, o.Platform) }, } diff --git a/cmd/cosign/cli/copy/copy.go b/cmd/cosign/cli/copy/copy.go index 0c0d6d2b975..9b8c0e27605 100644 --- a/cmd/cosign/cli/copy/copy.go +++ b/cmd/cosign/cli/copy/copy.go @@ -26,6 +26,7 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" + "github.com/sigstore/cosign/v2/cmd/cosign/cli/common" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/oci" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" @@ -35,7 +36,7 @@ import ( // CopyCmd implements the logic to copy the supplied container image and signatures. // nolint -func CopyCmd(ctx context.Context, regOpts options.RegistryOptions, srcImg, dstImg string, sigOnly, force bool) error { +func CopyCmd(ctx context.Context, regOpts options.RegistryOptions, srcImg, dstImg string, sigOnly, force bool, platform string) error { no := regOpts.NameOptions() srcRef, err := name.ParseReference(srcImg, no...) if err != nil { @@ -71,6 +72,43 @@ func CopyCmd(ctx context.Context, regOpts options.RegistryOptions, srcImg, dstIm return err } + idx, isIndex := root.(oci.SignedImageIndex) + + if platform != "" && !isIndex { + return fmt.Errorf("specified reference is not a multiarch image") + } + + if platform != "" && isIndex { + targetPlatform, err := v1.ParsePlatform(platform) + if err != nil { + return fmt.Errorf("parsing platform: %w", err) + } + platforms, err := common.GetIndexPlatforms(idx) + if err != nil { + return fmt.Errorf("getting available platforms: %w", err) + } + + platforms = common.MatchPlatform(targetPlatform, platforms) + if len(platforms) == 0 { + return fmt.Errorf("unable to find an SBOM for %s", targetPlatform.String()) + } + if len(platforms) > 1 { + return fmt.Errorf( + "platform spec matches more than one image architecture: %s", + platforms.String(), + ) + } + + nroot, err := idx.SignedImage(platforms[0].Hash) + if err != nil { + return fmt.Errorf("searching for %s image: %w", platforms[0].Hash.String(), err) + } + if nroot == nil { + return fmt.Errorf("unable to find image %s", platforms[0].Hash.String()) + } + root = nroot + } + if err := walk.SignedEntity(gctx, root, func(ctx context.Context, se oci.SignedEntity) error { // Both of the SignedEntity types implement Digest() h, err := se.Digest() diff --git a/cmd/cosign/cli/copy/copy_test.go b/cmd/cosign/cli/copy/copy_test.go index e1b4671f09c..8021fdf9c07 100644 --- a/cmd/cosign/cli/copy/copy_test.go +++ b/cmd/cosign/cli/copy/copy_test.go @@ -33,7 +33,7 @@ func TestCopyAttachmentTagPrefix(t *testing.T) { err := CopyCmd(ctx, options.RegistryOptions{ RefOpts: refOpts, - }, srcImg, destImg, false, true) + }, srcImg, destImg, false, true, "") if err == nil { t.Fatal("failed to copy with attachment-tag-prefix") } From 1ab36b7f84507a47b69e8ab747cbf13dfcd21b03 Mon Sep 17 00:00:00 2001 From: Vishal Choudhary Date: Sat, 16 Sep 2023 18:59:10 +0530 Subject: [PATCH 4/8] refactor: add platform package in pkg/oci Signed-off-by: Vishal Choudhary --- cmd/cosign/cli/copy/copy.go | 40 ++-------------- cmd/cosign/cli/download/attestation.go | 43 ++--------------- cmd/cosign/cli/download/sbom.go | 44 +++-------------- .../common => pkg/oci/platform}/platform.go | 48 ++++++++++++++++++- 4 files changed, 60 insertions(+), 115 deletions(-) rename {cmd/cosign/cli/common => pkg/oci/platform}/platform.go (58%) diff --git a/cmd/cosign/cli/copy/copy.go b/cmd/cosign/cli/copy/copy.go index 9b8c0e27605..462c57d282d 100644 --- a/cmd/cosign/cli/copy/copy.go +++ b/cmd/cosign/cli/copy/copy.go @@ -26,9 +26,9 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/remote/transport" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/common" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/oci" + ociplatform "github.com/sigstore/cosign/v2/pkg/oci/platform" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" "github.com/sigstore/cosign/v2/pkg/oci/walk" "golang.org/x/sync/errgroup" @@ -72,41 +72,9 @@ func CopyCmd(ctx context.Context, regOpts options.RegistryOptions, srcImg, dstIm return err } - idx, isIndex := root.(oci.SignedImageIndex) - - if platform != "" && !isIndex { - return fmt.Errorf("specified reference is not a multiarch image") - } - - if platform != "" && isIndex { - targetPlatform, err := v1.ParsePlatform(platform) - if err != nil { - return fmt.Errorf("parsing platform: %w", err) - } - platforms, err := common.GetIndexPlatforms(idx) - if err != nil { - return fmt.Errorf("getting available platforms: %w", err) - } - - platforms = common.MatchPlatform(targetPlatform, platforms) - if len(platforms) == 0 { - return fmt.Errorf("unable to find an SBOM for %s", targetPlatform.String()) - } - if len(platforms) > 1 { - return fmt.Errorf( - "platform spec matches more than one image architecture: %s", - platforms.String(), - ) - } - - nroot, err := idx.SignedImage(platforms[0].Hash) - if err != nil { - return fmt.Errorf("searching for %s image: %w", platforms[0].Hash.String(), err) - } - if nroot == nil { - return fmt.Errorf("unable to find image %s", platforms[0].Hash.String()) - } - root = nroot + root, err = ociplatform.SignedEntityForPlatform(root, platform) + if err != nil { + return err } if err := walk.SignedEntity(gctx, root, func(ctx context.Context, se oci.SignedEntity) error { diff --git a/cmd/cosign/cli/download/attestation.go b/cmd/cosign/cli/download/attestation.go index 4784090f714..1861494aadc 100644 --- a/cmd/cosign/cli/download/attestation.go +++ b/cmd/cosign/cli/download/attestation.go @@ -21,11 +21,9 @@ import ( "fmt" "github.com/google/go-containerregistry/pkg/name" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/common" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/cosign" - "github.com/sigstore/cosign/v2/pkg/oci" + "github.com/sigstore/cosign/v2/pkg/oci/platform" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" ) @@ -52,42 +50,9 @@ func AttestationCmd(ctx context.Context, regOpts options.RegistryOptions, attOpt return err } - idx, isIndex := se.(oci.SignedImageIndex) - - // We only allow --platform on multiarch indexes - if attOptions.Platform != "" && !isIndex { - return fmt.Errorf("specified reference is not a multiarch image") - } - - if attOptions.Platform != "" && isIndex { - targetPlatform, err := v1.ParsePlatform(attOptions.Platform) - if err != nil { - return fmt.Errorf("parsing platform: %w", err) - } - platforms, err := common.GetIndexPlatforms(idx) - if err != nil { - return fmt.Errorf("getting available platforms: %w", err) - } - - platforms = common.MatchPlatform(targetPlatform, platforms) - if len(platforms) == 0 { - return fmt.Errorf("unable to find an attestation for %s", targetPlatform.String()) - } - if len(platforms) > 1 { - return fmt.Errorf( - "platform spec matches more than one image architecture: %s", - platforms.String(), - ) - } - - nse, err := idx.SignedImage(platforms[0].Hash) - if err != nil { - return fmt.Errorf("searching for %s image: %w", platforms[0].Hash.String(), err) - } - if nse == nil { - return fmt.Errorf("unable to find image %s", platforms[0].Hash.String()) - } - se = nse + se, err = platform.SignedEntityForPlatform(se, attOptions.Platform) + if err != nil { + return err } attestations, err := cosign.FetchAttestations(se, predicateType) diff --git a/cmd/cosign/cli/download/sbom.go b/cmd/cosign/cli/download/sbom.go index 84bd300e21f..2e3c41a1c97 100644 --- a/cmd/cosign/cli/download/sbom.go +++ b/cmd/cosign/cli/download/sbom.go @@ -23,10 +23,9 @@ import ( "os" "github.com/google/go-containerregistry/pkg/name" - v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/sigstore/cosign/v2/cmd/cosign/cli/common" "github.com/sigstore/cosign/v2/cmd/cosign/cli/options" "github.com/sigstore/cosign/v2/pkg/oci" + "github.com/sigstore/cosign/v2/pkg/oci/platform" ociremote "github.com/sigstore/cosign/v2/pkg/oci/remote" ) @@ -49,43 +48,12 @@ func SBOMCmd( return nil, err } - idx, isIndex := se.(oci.SignedImageIndex) - - // We only allow --platform on multiarch indexes - if dnOpts.Platform != "" && !isIndex { - return nil, fmt.Errorf("specified reference is not a multiarch image") + se, err = platform.SignedEntityForPlatform(se, dnOpts.Platform) + if err != nil { + return nil, err } - if dnOpts.Platform != "" && isIndex { - targetPlatform, err := v1.ParsePlatform(dnOpts.Platform) - if err != nil { - return nil, fmt.Errorf("parsing platform: %w", err) - } - platforms, err := common.GetIndexPlatforms(idx) - if err != nil { - return nil, fmt.Errorf("getting available platforms: %w", err) - } - - platforms = common.MatchPlatform(targetPlatform, platforms) - if len(platforms) == 0 { - return nil, fmt.Errorf("unable to find an SBOM for %s", targetPlatform.String()) - } - if len(platforms) > 1 { - return nil, fmt.Errorf( - "platform spec matches more than one image architecture: %s", - platforms.String(), - ) - } - - nse, err := idx.SignedImage(platforms[0].Hash) - if err != nil { - return nil, fmt.Errorf("searching for %s image: %w", platforms[0].Hash.String(), err) - } - if nse == nil { - return nil, fmt.Errorf("unable to find image %s", platforms[0].Hash.String()) - } - se = nse - } + idx, isIndex := se.(oci.SignedImageIndex) file, err := se.Attachment("sbom") if errors.Is(err, ociremote.ErrImageNotFound) { @@ -93,7 +61,7 @@ func SBOMCmd( return nil, errors.New("no sbom attached to reference") } // Help the user with the available architectures - pl, err := common.GetIndexPlatforms(idx) + pl, err := platform.GetIndexPlatforms(idx) if len(pl) > 0 && err == nil { fmt.Fprintf( os.Stderr, diff --git a/cmd/cosign/cli/common/platform.go b/pkg/oci/platform/platform.go similarity index 58% rename from cmd/cosign/cli/common/platform.go rename to pkg/oci/platform/platform.go index 4d59e5e0fa9..f7cfddbddf4 100644 --- a/cmd/cosign/cli/common/platform.go +++ b/pkg/oci/platform/platform.go @@ -1,4 +1,4 @@ -package common +package platform import ( "fmt" @@ -43,7 +43,7 @@ func GetIndexPlatforms(idx oci.SignedImageIndex) (platformList, error) { // matchPlatform filters a list of platforms returning only those matching // a base. "Based" on ko's internal equivalent while it moves to GGCR. // https://github.com/google/ko/blob/e6a7a37e26d82a8b2bb6df991c5a6cf6b2728794/pkg/build/gobuild.go#L1020 -func MatchPlatform(base *v1.Platform, list platformList) platformList { +func matchPlatform(base *v1.Platform, list platformList) platformList { ret := platformList{} for _, p := range list { if base.OS != "" && base.OS != p.Platform.OS { @@ -74,3 +74,47 @@ func MatchPlatform(base *v1.Platform, list platformList) platformList { return ret } + +func SignedEntityForPlatform(se oci.SignedEntity, platform string) (oci.SignedEntity, error) { + if platform == "" { + // Copy all platforms + return se, nil + } + idx, isIndex := se.(oci.SignedImageIndex) + + // We only allow --platform on multiarch indexes + if !isIndex { + return nil, fmt.Errorf("specified reference is not a multiarch image") + } + + targetPlatform, err := v1.ParsePlatform(platform) + if err != nil { + return nil, fmt.Errorf("parsing platform: %w", err) + } + platforms, err := GetIndexPlatforms(idx) + if err != nil { + return nil, fmt.Errorf("getting available platforms: %w", err) + } + + platforms = matchPlatform(targetPlatform, platforms) + if len(platforms) == 0 { + return nil, fmt.Errorf("unable to find an entity for %s", targetPlatform.String()) + } + if len(platforms) > 1 { + return nil, fmt.Errorf( + "platform spec matches more than one image architecture: %s", + platforms.String(), + ) + } + + nse, err := idx.SignedImage(platforms[0].Hash) + if err != nil { + return nil, fmt.Errorf("searching for %s image: %w", platforms[0].Hash.String(), err) + } + if nse == nil { + return nil, fmt.Errorf("unable to find image %s", platforms[0].Hash.String()) + } + + return nse, nil + +} From 65ae8d0431766ccf00c4ea2bc9ea99075050006d Mon Sep 17 00:00:00 2001 From: Vishal Choudhary Date: Sat, 16 Sep 2023 19:02:57 +0530 Subject: [PATCH 5/8] feat: add a unit test Signed-off-by: Vishal Choudhary --- cmd/cosign/cli/copy/copy_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/cmd/cosign/cli/copy/copy_test.go b/cmd/cosign/cli/copy/copy_test.go index 8021fdf9c07..f89c993bfd2 100644 --- a/cmd/cosign/cli/copy/copy_test.go +++ b/cmd/cosign/cli/copy/copy_test.go @@ -38,3 +38,15 @@ func TestCopyAttachmentTagPrefix(t *testing.T) { t.Fatal("failed to copy with attachment-tag-prefix") } } + +func TestCopyPlatformOpt(t *testing.T) { + ctx := context.Background() + + srcImg := "alpine" + destImg := "test-alpine" + + err := CopyCmd(ctx, options.RegistryOptions{}, srcImg, destImg, false, true, "linux/amd64") + if err == nil { + t.Fatal("failed to copy with platform") + } +} From f0f944517dac8e5ce9c2f8d034040b46bac722a3 Mon Sep 17 00:00:00 2001 From: Vishal Choudhary Date: Sat, 16 Sep 2023 19:03:57 +0530 Subject: [PATCH 6/8] feat: add boilerplate Signed-off-by: Vishal Choudhary --- pkg/oci/platform/platform.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pkg/oci/platform/platform.go b/pkg/oci/platform/platform.go index f7cfddbddf4..e77d3d805c5 100644 --- a/pkg/oci/platform/platform.go +++ b/pkg/oci/platform/platform.go @@ -1,3 +1,17 @@ +// Copyright 2023 the Sigstore Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package platform import ( From 2bd30e5218e3de38496bedd1f9d054e582fd31ab Mon Sep 17 00:00:00 2001 From: Vishal Choudhary Date: Sat, 16 Sep 2023 19:12:21 +0530 Subject: [PATCH 7/8] feat: export platform list type Signed-off-by: Vishal Choudhary --- pkg/oci/platform/platform.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/oci/platform/platform.go b/pkg/oci/platform/platform.go index e77d3d805c5..b4f8cc451eb 100644 --- a/pkg/oci/platform/platform.go +++ b/pkg/oci/platform/platform.go @@ -22,12 +22,12 @@ import ( "github.com/sigstore/cosign/v2/pkg/oci" ) -type platformList []struct { +type PlatformList []struct { Hash v1.Hash Platform *v1.Platform } -func (pl *platformList) String() string { +func (pl *PlatformList) String() string { r := []string{} for _, p := range *pl { r = append(r, p.Platform.String()) @@ -35,13 +35,13 @@ func (pl *platformList) String() string { return strings.Join(r, ", ") } -func GetIndexPlatforms(idx oci.SignedImageIndex) (platformList, error) { +func GetIndexPlatforms(idx oci.SignedImageIndex) (PlatformList, error) { im, err := idx.IndexManifest() if err != nil { return nil, fmt.Errorf("fetching index manifest: %w", err) } - platforms := platformList{} + platforms := PlatformList{} for _, m := range im.Manifests { if m.Platform == nil { continue @@ -57,8 +57,8 @@ func GetIndexPlatforms(idx oci.SignedImageIndex) (platformList, error) { // matchPlatform filters a list of platforms returning only those matching // a base. "Based" on ko's internal equivalent while it moves to GGCR. // https://github.com/google/ko/blob/e6a7a37e26d82a8b2bb6df991c5a6cf6b2728794/pkg/build/gobuild.go#L1020 -func matchPlatform(base *v1.Platform, list platformList) platformList { - ret := platformList{} +func matchPlatform(base *v1.Platform, list PlatformList) PlatformList { + ret := PlatformList{} for _, p := range list { if base.OS != "" && base.OS != p.Platform.OS { continue From dab95a2262d3aca7ae2239cc9f0bb987fe0a334f Mon Sep 17 00:00:00 2001 From: Vishal Choudhary Date: Sat, 16 Sep 2023 19:14:37 +0530 Subject: [PATCH 8/8] lint: fix linting errors Signed-off-by: Vishal Choudhary --- pkg/oci/platform/platform.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/pkg/oci/platform/platform.go b/pkg/oci/platform/platform.go index b4f8cc451eb..a2939d73660 100644 --- a/pkg/oci/platform/platform.go +++ b/pkg/oci/platform/platform.go @@ -22,12 +22,12 @@ import ( "github.com/sigstore/cosign/v2/pkg/oci" ) -type PlatformList []struct { +type List []struct { Hash v1.Hash Platform *v1.Platform } -func (pl *PlatformList) String() string { +func (pl *List) String() string { r := []string{} for _, p := range *pl { r = append(r, p.Platform.String()) @@ -35,13 +35,13 @@ func (pl *PlatformList) String() string { return strings.Join(r, ", ") } -func GetIndexPlatforms(idx oci.SignedImageIndex) (PlatformList, error) { +func GetIndexPlatforms(idx oci.SignedImageIndex) (List, error) { im, err := idx.IndexManifest() if err != nil { return nil, fmt.Errorf("fetching index manifest: %w", err) } - platforms := PlatformList{} + platforms := List{} for _, m := range im.Manifests { if m.Platform == nil { continue @@ -57,8 +57,8 @@ func GetIndexPlatforms(idx oci.SignedImageIndex) (PlatformList, error) { // matchPlatform filters a list of platforms returning only those matching // a base. "Based" on ko's internal equivalent while it moves to GGCR. // https://github.com/google/ko/blob/e6a7a37e26d82a8b2bb6df991c5a6cf6b2728794/pkg/build/gobuild.go#L1020 -func matchPlatform(base *v1.Platform, list PlatformList) PlatformList { - ret := PlatformList{} +func matchPlatform(base *v1.Platform, list List) List { + ret := List{} for _, p := range list { if base.OS != "" && base.OS != p.Platform.OS { continue @@ -130,5 +130,4 @@ func SignedEntityForPlatform(se oci.SignedEntity, platform string) (oci.SignedEn } return nse, nil - }