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 17, 2024
1 parent 071f060 commit 641cbba
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 1 deletion.
29 changes: 28 additions & 1 deletion cmd/oras/root/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ limitations under the License.
package root

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

"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"oras.land/oras-go/v2"
Expand All @@ -41,6 +44,7 @@ import (
type pushOptions struct {
option.Common
option.Packer
option.Platform
option.ImageSpec
option.Target
option.Format
Expand Down Expand Up @@ -97,6 +101,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 repository with platform:
oras push --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 +123,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 All @@ -136,6 +143,9 @@ Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with t
if opts.manifestConfigRef != "" && opts.artifactType != "" {
return errors.New("--artifact-type and --config cannot both be provided for 1.0 OCI image")
}
if opts.Platform.Platform != nil && opts.artifactType != "" {
return errors.New("--artifact-type and --platform cannot both be provided for 1.0 OCI image")
}
case oras.PackManifestVersion1_1:
if opts.manifestConfigRef == "" && opts.artifactType == "" {
opts.artifactType = oras.MediaTypeUnknownArtifact
Expand All @@ -152,6 +162,7 @@ Example - Push file "hi.txt" into an OCI image layout folder 'layout-dir' with t
cmd.Flags().IntVarP(&opts.concurrency, "concurrency", "", 5, "concurrency level")
opts.SetTypes(option.FormatTypeText, option.FormatTypeJSON, option.FormatTypeGoTemplate)
option.ApplyFlags(&opts, cmd.Flags())
cmd.MarkFlagsMutuallyExclusive("config", "platform")
return oerrors.Command(cmd, &opts.Target)
}

Expand Down Expand Up @@ -183,6 +194,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
}
desc := ocispec.Descriptor{
MediaType: oras.MediaTypeUnknownConfig,
Digest: digest.FromBytes(blob),
Size: int64(len(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:e94c0ba80a11")
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
46 changes: 46 additions & 0 deletions test/e2e/suite/command/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,22 @@ 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 --platform and --artifact-type at the same time for OCI spec v1.0 registry", func() {
tempDir := PrepareTempFiles()
repo := pushTestRepo("no-mediatype")
ref := RegistryRef(ZOTHost, repo, "")

ORAS("push", ref, "--platform", "linux/amd64", "--artifact-type", "test/artifact+json", "--image-spec", "v1.0").ExpectFailure().WithWorkDir(tempDir).Exec()
})

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

ORAS("push", ref, "--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 +132,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 --platform and without --artifactType", func() {
testRepo := pushTestRepo("v1-1/no-artifact-type")
subjectRef := RegistryRef(ZOTHost, testRepo, foobar.Tag)
imageSpecFlag := "v1.1"
ORAS("push", subjectRef, "--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 +638,26 @@ 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, "--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 customized manifest annotation", func() {
tempDir := PrepareTempFiles()
ref := LayoutRef(tempDir, tag)
Expand Down

0 comments on commit 641cbba

Please sign in to comment.