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 <[email protected]>
  • Loading branch information
TerryHowe committed Oct 16, 2024
1 parent 1d60e22 commit 0788180
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 1 deletion.
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", "", "", 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)
}
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 @@ 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 @@ -100,6 +103,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 Down Expand Up @@ -133,6 +139,10 @@ Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with t
}
}
}
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 @@ 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 := opts.artifactType
if mediaType == "" {
mediaType = oras.MediaTypeUnknownConfig
}
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
12 changes: 12 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,18 @@ 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.artifact.v1",
}

PlatformV10ConfigSize = 38
PlatformV10ConfigDigest = digest.Digest("sha256:e94c0ba80a1157ffab5b5c6656fffc089c6446c7ed0604f3382910d1ef7dd40d")
PlatformV10ConfigStateKey = match.StateKey{
Digest: "e94c0ba80a11", Name: "test/artifact+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.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: "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

0 comments on commit 0788180

Please sign in to comment.