Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add platform option to push command #1500

Merged
merged 1 commit into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion cmd/oras/internal/option/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (opts *Platform) ApplyFlags(fs *pflag.FlagSet) {
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.
// Parse parses the input platform flag to an oci platform type.
func (opts *Platform) Parse(*cobra.Command) error {
if opts.platform == "" {
return nil
Expand Down Expand Up @@ -73,3 +73,19 @@ func (opts *Platform) Parse(*cobra.Command) error {
opts.Platform = &p
return nil
}

// ArtifactPlatform option struct.
type ArtifactPlatform struct {
Platform
}

// ApplyFlags applies flags to a command flag set.
func (opts *ArtifactPlatform) ApplyFlags(fs *pflag.FlagSet) {
opts.FlagDescription = "set artifact platform"
fs.StringVarP(&opts.platform, "artifact-platform", "", "", "[Experimental] "+opts.FlagDescription+" in the form of `os[/arch][/variant][:os_version]`")
}

// Parse parses the input platform flag to an oci platform type.
func (opts *ArtifactPlatform) Parse(cmd *cobra.Command) error {
return opts.Platform.Parse(cmd)
}
shizhMSFT marked this conversation as resolved.
Show resolved Hide resolved
26 changes: 26 additions & 0 deletions cmd/oras/root/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
package root

import (
"bytes"
"encoding/json"
"errors"
"strings"

Expand All @@ -41,6 +43,7 @@
type pushOptions struct {
option.Common
option.Packer
option.ArtifactPlatform
option.ImageSpec
option.Target
option.Format
Expand Down Expand Up @@ -100,6 +103,9 @@
Example - Push repository with manifest annotation file:
oras push --annotation-file annotation.json localhost:5000/hello:v1

Example - Push artifact to repository with platform:
oras push --artifact-platform linux/arm/v5 localhost:5000/hello:v1

Example - Push file "hi.txt" with multiple tags:
oras push localhost:5000/hello:tag1,tag2,tag3 hi.txt

Expand Down Expand Up @@ -133,6 +139,10 @@
}
}
}
configAndPlatform := []string{"config", "artifact-platform"}
if err := oerrors.CheckMutuallyExclusiveFlags(cmd.Flags(), configAndPlatform...); err != nil {
return err
}

switch opts.PackVersion {
case oras.PackManifestVersion1_0:
Expand Down Expand Up @@ -182,6 +192,22 @@
}
desc.Annotations = packOpts.ConfigAnnotations
packOpts.ConfigDescriptor = &desc
} else if opts.Platform.Platform != nil {
blob, err := json.Marshal(opts.Platform.Platform)
if err != nil {
return err

Check warning on line 198 in cmd/oras/root/push.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/push.go#L198

Added line #L198 was not covered by tests
}
mediaType := oras.MediaTypeUnknownConfig
if opts.Flag == option.ImageSpecV1_0 && opts.artifactType != "" {
mediaType = opts.artifactType
}
desc := content.NewDescriptorFromBytes(mediaType, blob)
TerryHowe marked this conversation as resolved.
Show resolved Hide resolved
err = store.Push(ctx, desc, bytes.NewReader(blob))
if err != nil {
return err

Check warning on line 207 in cmd/oras/root/push.go

View check run for this annotation

Codecov / codecov/patch

cmd/oras/root/push.go#L207

Added line #L207 was not covered by tests
}
desc.Annotations = packOpts.ConfigAnnotations
packOpts.ConfigDescriptor = &desc
}
memoryStore := memory.New()
union := contentutil.MultiReadOnlyTarget(memoryStore, store)
Expand Down
15 changes: 15 additions & 0 deletions test/e2e/internal/testdata/foobar/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ var (
Digest: "46b68ac1696c", Name: "application/vnd.unknown.config.v1+json",
}

PlatformConfigSize = 38
PlatformConfigDigest = digest.Digest("sha256:e94c0ba80a1157ffab5b5c6656fffc089c6446c7ed0604f3382910d1ef7dd40d")
PlatformConfigStateKey = match.StateKey{
Digest: "e94c0ba80a11", Name: "application/vnd.unknown.config.v1+json",
}

PlatformV10ConfigSize = 38
PlatformV10ConfigDigest = digest.Digest("sha256:e94c0ba80a1157ffab5b5c6656fffc089c6446c7ed0604f3382910d1ef7dd40d")
PlatformV10ConfigStateKey = match.StateKey{
Digest: "e94c0ba80a11", Name: "test/artifact+json",
}
PlatformV1DEfaultConfigStateKey = match.StateKey{
Digest: "e94c0ba80a11", Name: "application/vnd.unknown.config.v1+json",
}

FileBarName = "foobar/bar"
FileBarStateKey = match.StateKey{Digest: "fcde2b2edba5", Name: FileLayerNames[2]}
FileStateKeys = []match.StateKey{
Expand Down
68 changes: 68 additions & 0 deletions test/e2e/suite/command/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ var _ = Describe("ORAS beginners:", func() {
ORAS("push", ref, "--config", foobar.FileConfigName, "--artifact-type", "test/artifact+json", "--image-spec", "v1.0").ExpectFailure().WithWorkDir(tempDir).Exec()
})

It("should fail to use --artifact-platform and --config at the same time", func() {
tempDir := PrepareTempFiles()
repo := pushTestRepo("no-mediatype")
ref := RegistryRef(ZOTHost, repo, "")

ORAS("push", ref, "--artifact-platform", "linux/amd64", "--config", foobar.FileConfigName).ExpectFailure().WithWorkDir(tempDir).Exec()
})

It("should fail if image spec is not valid", func() {
testRepo := attachTestRepo("invalid-image-spec")
subjectRef := RegistryRef(ZOTHost, testRepo, foobar.Tag)
Expand Down Expand Up @@ -612,6 +620,66 @@ var _ = Describe("OCI image layout users:", func() {
}))
})

It("should push files with platform", func() {
tempDir := PrepareTempFiles()
ref := LayoutRef(tempDir, tag)
ORAS("push", Flags.Layout, ref, "--artifact-platform", "darwin/arm64", foobar.FileBarName, "-v").
MatchStatus([]match.StateKey{
foobar.PlatformConfigStateKey,
foobar.FileBarStateKey,
}, true, 2).
WithWorkDir(tempDir).Exec()
// validate
fetched := ORAS("manifest", "fetch", Flags.Layout, ref).Exec().Out.Contents()
var manifest ocispec.Manifest
Expect(json.Unmarshal(fetched, &manifest)).ShouldNot(HaveOccurred())
Expect(manifest.Config).Should(Equal(ocispec.Descriptor{
MediaType: foobar.PlatformConfigStateKey.Name,
Size: int64(foobar.PlatformConfigSize),
Digest: foobar.PlatformConfigDigest,
}))
})

It("should push files with platform with mediaType as artifactType for v1.0", func() {
tempDir := PrepareTempFiles()
ref := LayoutRef(tempDir, tag)
ORAS("push", Flags.Layout, ref, "--image-spec", "v1.0", "--artifact-type", "test/artifact+json", "--artifact-platform", "darwin/arm64", foobar.FileBarName, "-v").
MatchStatus([]match.StateKey{
foobar.PlatformV10ConfigStateKey,
foobar.FileBarStateKey,
}, true, 2).
WithWorkDir(tempDir).Exec()
// validate
fetched := ORAS("manifest", "fetch", Flags.Layout, ref).Exec().Out.Contents()
var manifest ocispec.Manifest
Expect(json.Unmarshal(fetched, &manifest)).ShouldNot(HaveOccurred())
Expect(manifest.Config).Should(Equal(ocispec.Descriptor{
MediaType: "test/artifact+json",
Size: int64(foobar.PlatformV10ConfigSize),
Digest: foobar.PlatformV10ConfigDigest,
}))
})

It("should push files with platform with no artifactType for v1.0", func() {
tempDir := PrepareTempFiles()
ref := LayoutRef(tempDir, tag)
ORAS("push", Flags.Layout, ref, "--image-spec", "v1.0", "--artifact-platform", "darwin/arm64", foobar.FileBarName, "-v").
MatchStatus([]match.StateKey{
foobar.PlatformV1DEfaultConfigStateKey,
foobar.FileBarStateKey,
}, true, 2).
WithWorkDir(tempDir).Exec()
// validate
fetched := ORAS("manifest", "fetch", Flags.Layout, ref).Exec().Out.Contents()
var manifest ocispec.Manifest
Expect(json.Unmarshal(fetched, &manifest)).ShouldNot(HaveOccurred())
Expect(manifest.Config).Should(Equal(ocispec.Descriptor{
MediaType: "application/vnd.unknown.config.v1+json",
Size: int64(foobar.PlatformV10ConfigSize),
Digest: foobar.PlatformV10ConfigDigest,
}))
})

It("should push files with customized manifest annotation", func() {
tempDir := PrepareTempFiles()
ref := LayoutRef(tempDir, tag)
Expand Down
Loading