From de4cb156d2afedd2668be0fe8d625a34a2ac235d Mon Sep 17 00:00:00 2001 From: Billy Zha Date: Sun, 7 Apr 2024 20:30:49 +0800 Subject: [PATCH] feat: support --platform for oras attach (#1309) Signed-off-by: Billy Zha --- cmd/oras/internal/option/platform.go | 10 +++++--- cmd/oras/internal/option/platform_test.go | 10 ++++---- cmd/oras/root/attach.go | 11 +++++++-- test/e2e/suite/command/attach.go | 28 +++++++++++++++++++++++ 4 files changed, 49 insertions(+), 10 deletions(-) diff --git a/cmd/oras/internal/option/platform.go b/cmd/oras/internal/option/platform.go index 96954ff01..37cbea1d9 100644 --- a/cmd/oras/internal/option/platform.go +++ b/cmd/oras/internal/option/platform.go @@ -27,13 +27,17 @@ import ( // Platform option struct. type Platform struct { - platform string - Platform *ocispec.Platform + platform string + Platform *ocispec.Platform + FlagDescription string } // ApplyFlags applies flags to a command flag set. func (opts *Platform) ApplyFlags(fs *pflag.FlagSet) { - fs.StringVarP(&opts.platform, "platform", "", "", "request platform in the form of `os[/arch][/variant][:os_version]`") + if opts.FlagDescription == "" { + opts.FlagDescription = "request platform" + } + fs.StringVarP(&opts.platform, "platform", "", "", opts.FlagDescription+" in the form of `os[/arch][/variant][:os_version]`") } // parse parses the input platform flag to an oci platform type. diff --git a/cmd/oras/internal/option/platform_test.go b/cmd/oras/internal/option/platform_test.go index 9af257aa6..c12ae3b86 100644 --- a/cmd/oras/internal/option/platform_test.go +++ b/cmd/oras/internal/option/platform_test.go @@ -35,11 +35,11 @@ func TestPlatform_Parse_err(t *testing.T) { name string opts *Platform }{ - {name: "empty arch 1", opts: &Platform{"os/", nil}}, - {name: "empty arch 2", opts: &Platform{"os//variant", nil}}, - {name: "empty os", opts: &Platform{"/arch", nil}}, - {name: "empty os with variant", opts: &Platform{"/arch/variant", nil}}, - {name: "trailing slash", opts: &Platform{"os/arch/variant/llama", nil}}, + {name: "empty arch 1", opts: &Platform{"os/", nil, ""}}, + {name: "empty arch 2", opts: &Platform{"os//variant", nil, ""}}, + {name: "empty os", opts: &Platform{"/arch", nil, ""}}, + {name: "empty os with variant", opts: &Platform{"/arch/variant", nil, ""}}, + {name: "trailing slash", opts: &Platform{"os/arch/variant/llama", nil, ""}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/cmd/oras/root/attach.go b/cmd/oras/root/attach.go index 2c9c6c08c..f55a590d9 100644 --- a/cmd/oras/root/attach.go +++ b/cmd/oras/root/attach.go @@ -39,6 +39,7 @@ type attachOptions struct { option.Packer option.Target option.Format + option.Platform artifactType string concurrency int @@ -56,6 +57,9 @@ func attachCmd() *cobra.Command { Example - Attach file 'hi.txt' with aritifact type 'doc/example' to manifest 'hello:v1' in registry 'localhost:5000': oras attach --artifact-type doc/example localhost:5000/hello:v1 hi.txt +Example - Attach file 'hi.txt' to a specific artifact with platform 'linux/amd64' in multi-arch index 'hello:v1' + oras attach --artifact-type doc/example --platform linux/amd64 localhost:5000/hello:v1 hi.txt + Example - Push file "hi.txt" with the custom layer media type 'application/vnd.me.hi': oras attach --artifact-type doc/example localhost:5000/hello:v1 hi.txt:application/vnd.me.hi @@ -94,6 +98,7 @@ Example - Attach file to the manifest tagged 'v1' in an OCI image layout folder cmd.Flags().StringVarP(&opts.artifactType, "artifact-type", "", "", "artifact type") cmd.Flags().IntVarP(&opts.concurrency, "concurrency", "", 5, "concurrency level") + opts.FlagDescription = "attach to an arch-specific subject" _ = cmd.MarkFlagRequired("artifact-type") opts.EnableDistributionSpecFlag() option.ApplyFlags(&opts, cmd.Flags()) @@ -132,9 +137,11 @@ func runAttach(cmd *cobra.Command, opts *attachOptions) error { // add both pull and push scope hints for dst repository // to save potential push-scope token requests during copy ctx = registryutil.WithScopeHint(ctx, dst, auth.ActionPull, auth.ActionPush) - subject, err := dst.Resolve(ctx, opts.Reference) + fetchOpts := oras.DefaultResolveOptions + fetchOpts.TargetPlatform = opts.Platform.Platform + subject, err := oras.Resolve(ctx, dst, opts.Reference, fetchOpts) if err != nil { - return err + return fmt.Errorf("failed to resolve %s: %w", opts.Reference, err) } descs, err := loadFiles(ctx, store, annotations, opts.FileRefs, displayStatus) if err != nil { diff --git a/test/e2e/suite/command/attach.go b/test/e2e/suite/command/attach.go index f25c7859f..26ad96931 100644 --- a/test/e2e/suite/command/attach.go +++ b/test/e2e/suite/command/attach.go @@ -29,6 +29,7 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" "oras.land/oras/test/e2e/internal/testdata/feature" "oras.land/oras/test/e2e/internal/testdata/foobar" + "oras.land/oras/test/e2e/internal/testdata/multi_arch" . "oras.land/oras/test/e2e/internal/utils" "oras.land/oras/test/e2e/internal/utils/match" ) @@ -99,6 +100,21 @@ var _ = Describe("1.1 registry users:", func() { MatchStatus([]match.StateKey{foobar.AttachFileStateKey}, false, 1).Exec() }) + It("should attach a file to an arch-specific subject", func() { + testRepo := attachTestRepo("arch-specific") + // Below line will cause unexpected 500 + // pending for https://github.com/project-zot/zot/pull/2351 to be released + // CopyZOTRepo(ImageRepo, testRepo) + subjectRef := RegistryRef(ZOTHost, testRepo, multi_arch.Tag) + ORAS("cp", RegistryRef(ZOTHost, ImageRepo, multi_arch.Tag), subjectRef).Exec() + artifactType := "test/attach" + // test + out := ORAS("attach", "--artifact-type", artifactType, subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--format", "{{.Digest}}", "--platform", "linux/amd64"). + WithWorkDir(PrepareTempFiles()).Exec().Out.Contents() + // validate + ORAS("discover", "--artifact-type", artifactType, RegistryRef(ZOTHost, testRepo, multi_arch.LinuxAMD64.Digest.String())).MatchKeyWords(string(out)).Exec() + }) + It("should attach a file to a subject and export the built manifest", func() { // prepare testRepo := attachTestRepo("export-manifest") @@ -282,6 +298,18 @@ var _ = Describe("OCI image layout users:", func() { fetched := ORAS("manifest", "fetch", Flags.Layout, LayoutRef(root, index.Manifests[0].Digest.String())).Exec().Out.Contents() MatchFile(filepath.Join(root, exportName), string(fetched), DefaultTimeout) }) + + It("should attach a file to an arch-specific subject", func() { + root := PrepareTempOCI(ImageRepo) + subjectRef := LayoutRef(root, multi_arch.Tag) + artifactType := "test/attach" + // test + out := ORAS("attach", Flags.Layout, "--artifact-type", artifactType, subjectRef, fmt.Sprintf("%s:%s", foobar.AttachFileName, foobar.AttachFileMedia), "--format", "{{.Digest}}", "--platform", "linux/amd64"). + WithWorkDir(PrepareTempFiles()).Exec().Out.Contents() + // validate + ORAS("discover", Flags.Layout, "--artifact-type", artifactType, LayoutRef(root, multi_arch.LinuxAMD64.Digest.String())).MatchKeyWords(string(out)).Exec() + }) + It("should attach a file via a OCI Image", func() { root := PrepareTempOCI(ImageRepo) subjectRef := LayoutRef(root, foobar.Tag)