Skip to content

Commit

Permalink
feature: add platform option to push command
Browse files Browse the repository at this point in the history
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
  • Loading branch information
TerryHowe committed Sep 23, 2024
1 parent 961e9f8 commit 3038b7b
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 2 deletions.
20 changes: 19 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,21 @@ 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) {
if opts.FlagDescription == "" {
opts.FlagDescription = "artifact platform"
}
fs.StringVarP(&opts.platform, "artifact-platform", "", "", 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)
}
31 changes: 30 additions & 1 deletion cmd/oras/root/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ limitations under the License.
package root

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

Expand All @@ -41,6 +43,7 @@ import (
type pushOptions struct {
option.Common
option.Packer
option.ArtifactPlatform
option.ImageSpec
option.Target
option.Format
Expand Down Expand Up @@ -97,6 +100,9 @@ Example - Push repository with manifest annotations:
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 All @@ -116,7 +122,7 @@ Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with t
return err
}

if opts.manifestConfigRef != "" && opts.artifactType == "" {
if (opts.manifestConfigRef != "" || opts.Platform.Platform != nil) && opts.artifactType == "" {
if !cmd.Flags().Changed("image-spec") {
// switch to v1.0 manifest since artifact type is suggested
// by OCI v1.1 artifact guidance but is not presented
Expand Down Expand Up @@ -156,6 +162,11 @@ Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with t
}

func runPush(cmd *cobra.Command, opts *pushOptions) error {
configAndPlatform := []string{"config", "artifact-platform"}
if err := oerrors.CheckMutuallyExclusiveFlags(cmd.Flags(), configAndPlatform...); err != nil {
return err
}

ctx, logger := command.GetLogger(cmd, &opts.Common)
annotations, err := opts.LoadManifestAnnotations()
if err != nil {
Expand Down Expand Up @@ -183,6 +194,24 @@ func runPush(cmd *cobra.Command, opts *pushOptions) error {
}
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
}
mediaType := oras.MediaTypeUnknownConfig
if opts.Flag == option.ImageSpecV1_0 {
if opts.artifactType != "" {
mediaType = opts.artifactType
}
}
desc := content.NewDescriptorFromBytes(mediaType, blob)
err = store.Push(ctx, desc, bytes.NewReader(blob))
if err != nil {
return err
}
desc.Annotations = packOpts.ConfigAnnotations
packOpts.ConfigDescriptor = &desc
}
memoryStore := memory.New()
union := contentutil.MultiReadOnlyTarget(memoryStore, store)
Expand Down
6 changes: 6 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,12 @@ 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",
}

FileBarName = "foobar/bar"
FileBarStateKey = match.StateKey{Digest: "fcde2b2edba5", Name: FileLayerNames[2]}
FileStateKeys = []match.StateKey{
Expand Down
58 changes: 58 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 @@ -116,6 +124,16 @@ var _ = Describe("ORAS beginners:", func() {
MatchErrKeyWords("missing artifact type for OCI image-spec v1.1 artifacts").
Exec()
})

It("should fail if image spec v1.1 is used, with --artifact-platform and without --artifactType", func() {
testRepo := pushTestRepo("v1-1/no-artifact-type")
subjectRef := RegistryRef(ZOTHost, testRepo, foobar.Tag)
imageSpecFlag := "v1.1"
ORAS("push", subjectRef, "--artifact-platform", "linux/amd64", Flags.ImageSpec, imageSpecFlag).
ExpectFailure().
MatchErrKeyWords("missing artifact type for OCI image-spec v1.1 artifacts").
Exec()
})
})
})

Expand Down Expand Up @@ -612,6 +630,46 @@ 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.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: "test/artifact+json",
Size: int64(foobar.PlatformConfigSize),
Digest: foobar.PlatformConfigDigest,
}))
})

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

0 comments on commit 3038b7b

Please sign in to comment.