Skip to content

Commit

Permalink
feat: support --platform for oras attach (#1309)
Browse files Browse the repository at this point in the history
Signed-off-by: Billy Zha <[email protected]>
  • Loading branch information
qweeah authored Apr 7, 2024
1 parent 4d808ac commit de4cb15
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 10 deletions.
10 changes: 7 additions & 3 deletions cmd/oras/internal/option/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
10 changes: 5 additions & 5 deletions cmd/oras/internal/option/platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
11 changes: 9 additions & 2 deletions cmd/oras/root/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type attachOptions struct {
option.Packer
option.Target
option.Format
option.Platform

artifactType string
concurrency int
Expand All @@ -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
Expand Down Expand Up @@ -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())
Expand Down Expand Up @@ -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 {
Expand Down
28 changes: 28 additions & 0 deletions test/e2e/suite/command/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit de4cb15

Please sign in to comment.