Skip to content

Commit

Permalink
feat: add compatibility mode for push and attach cmd (oras-project#741)
Browse files Browse the repository at this point in the history
Resolves oras-project#739

Signed-off-by: Billy Zha <[email protected]>
  • Loading branch information
qweeah authored and Terry Howe committed Feb 2, 2023
1 parent e006710 commit eda5207
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 9 deletions.
26 changes: 20 additions & 6 deletions cmd/oras/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type attachOptions struct {
option.Common
option.Remote
option.Packer
option.ImageSpec
option.DistributionSpec

targetRef string
artifactType string
Expand All @@ -48,19 +50,27 @@ func attachCmd() *cobra.Command {
** This command is in preview and under development. **
Example - Attach file 'hi.txt' with type 'doc/example' to manifest 'hello:test' in registry 'localhost:5000'
Example - Attach file 'hi.txt' with type 'doc/example' to manifest 'hello:test' in registry 'localhost:5000':
oras attach --artifact-type doc/example localhost:5000/hello:test hi.txt
Example - Attach file 'hi.txt' and add annotations from file 'annotation.json'
Example - Attach file "hi.txt" with specific media type when building the manifest:
oras attach --artifact-type doc/example --image-spec v1.1-image localhost:5000/hello:test hi.txt # OCI image
oras attach --artifact-type doc/example --image-spec v1.1-artifact localhost:5000/hello:test hi.txt # OCI artifact
Example - Attach file "hi.txt" using a specific method for the Referrers API:
oras attach --artifact-type doc/example --distribution-spec v1.1-referrers-api localhost:5000/hello:test hi.txt # via API
oras attach --artifact-type doc/example --distribution-spec v1.1-referrers-tag localhost:5000/hello:test hi.txt # via tag scheme
Example - Attach file 'hi.txt' and add annotations from file 'annotation.json':
oras attach --artifact-type doc/example --annotation-file annotation.json localhost:5000/hello:latest hi.txt
Example - Attach an artifact with manifest annotations
Example - Attach an artifact with manifest annotations:
oras attach --artifact-type doc/example --annotation "key1=val1" --annotation "key2=val2" localhost:5000/hello:latest
Example - Attach file 'hi.txt' and add manifest annotations
Example - Attach file 'hi.txt' and add manifest annotations:
oras attach --artifact-type doc/example --annotation "key=val" localhost:5000/hello:latest hi.txt
Example - Attach file 'hi.txt' and export the pushed manifest to 'manifest.json'
Example - Attach file 'hi.txt' and export the pushed manifest to 'manifest.json':
oras attach --artifact-type doc/example --export-manifest manifest.json localhost:5000/hello:latest hi.txt
`,
Args: cobra.MinimumNArgs(1),
Expand Down Expand Up @@ -103,6 +113,9 @@ func runAttach(opts attachOptions) error {
if dst.Reference.Reference == "" {
return oerrors.NewErrInvalidReference(dst.Reference)
}
if opts.ReferrersAPI != nil {
dst.SetReferrersCapability(*opts.ReferrersAPI)
}
subject, err := dst.Resolve(ctx, dst.Reference.Reference)
if err != nil {
return err
Expand All @@ -116,6 +129,7 @@ func runAttach(opts attachOptions) error {
packOpts := oras.PackOptions{
Subject: &subject,
ManifestAnnotations: annotations[option.AnnotationManifest],
PackImageManifest: opts.ManifestMediaType == ocispec.MediaTypeImageManifest,
}
pack := func() (ocispec.Descriptor, error) {
return oras.Pack(ctx, store, opts.artifactType, descs, packOpts)
Expand All @@ -137,7 +151,7 @@ func runAttach(opts attachOptions) error {
return oras.CopyGraph(ctx, store, dst, root, graphCopyOptions)
}

root, err := pushArtifact(dst, pack, &packOpts, copy, &graphCopyOptions, opts.Verbose)
root, err := pushArtifact(dst, pack, &packOpts, copy, &graphCopyOptions, opts.ManifestMediaType == "", opts.Verbose)
if err != nil {
return err
}
Expand Down
83 changes: 83 additions & 0 deletions cmd/oras/internal/option/spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
Copyright The ORAS Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package option

import (
"fmt"
"github.com/spf13/pflag"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)

// ImageSpec option struct.
type ImageSpec struct {
// Manifest type for building artifact
ManifestMediaType string

// specFlag should be provided in form of `<version>-<manifest type>`
specFlag string
}

// Parse parses flags into the option.
func (opts *ImageSpec) Parse() error {
switch opts.specFlag {
case "":
opts.ManifestMediaType = ""
case "v1.1-image":
opts.ManifestMediaType = ocispec.MediaTypeImageManifest
case "v1.1-artifact":
opts.ManifestMediaType = ocispec.MediaTypeArtifactManifest
default:
return fmt.Errorf("unknown image specification flag: %q", opts.specFlag)
}
return nil
}

// ApplyFlags applies flags to a command flag set.
func (opts *ImageSpec) ApplyFlags(fs *pflag.FlagSet) {
fs.StringVar(&opts.specFlag, "image-spec", "", "specify manifest type for building artifact. options: v1.1-image, v1.1-artifact")
}

// DistributionSpec option struct.
type DistributionSpec struct {
// ReferrersAPI indicates the preference of the implementation of the Referrers API.
// Set to true for referrers API, false for referrers tag scheme, and nil for auto fallback.
ReferrersAPI *bool

// specFlag should be provided in form of`<version>-<api>-<option>`
specFlag string
}

// Parse parses flags into the option.
func (opts *DistributionSpec) Parse() error {
switch opts.specFlag {
case "":
opts.ReferrersAPI = nil
case "v1.1-referrers-tag":
isApi := false
opts.ReferrersAPI = &isApi
case "v1.1-referrers-api":
isApi := true
opts.ReferrersAPI = &isApi
default:
return fmt.Errorf("unknown image specification flag: %q", opts.specFlag)
}
return nil
}

// ApplyFlags applies flags to a command flag set.
func (opts *DistributionSpec) ApplyFlags(fs *pflag.FlagSet) {
fs.StringVar(&opts.specFlag, "distribution-spec", "", "set OCI distribution spec version and API option. options: v1.1-referrers-api, v1.1-referrers-tag")
}
17 changes: 14 additions & 3 deletions cmd/oras/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type pushOptions struct {
option.Common
option.Remote
option.Packer
option.ImageSpec

targetRef string
extraRefs []string
Expand Down Expand Up @@ -73,6 +74,10 @@ Example - Push file "hi.txt" with config type "application/vnd.me.config":
Example - Push file "hi.txt" with the custom manifest config "config.json" of the custom media type "application/vnd.me.config":
oras push --config config.json:application/vnd.me.config localhost:5000/hello:latest hi.txt
Example - Push file "hi.txt" with specific media type when building the manifest:
oras push --image-spec v1.1-image localhost:5000/hello:latest hi.txt # OCI image
oras push --image-spec v1.1-artifact localhost:5000/hello:latest hi.txt # OCI artifact
Example - Push file to the insecure registry:
oras push --insecure localhost:5000/hello:latest hi.txt
Expand All @@ -99,6 +104,9 @@ Example - Push file "hi.txt" with multiple tags and concurrency level tuned:
return option.Parse(&opts)
},
RunE: func(cmd *cobra.Command, args []string) error {
if opts.ManifestMediaType == ocispec.MediaTypeArtifactManifest && opts.manifestConfigRef != "" {
return errors.New("cannot build an OCI artifact with manifest config")
}
refs := strings.Split(args[0], ",")
opts.targetRef = refs[0]
opts.extraRefs = refs[1:]
Expand Down Expand Up @@ -142,6 +150,9 @@ func runPush(opts pushOptions) error {
packOpts.ConfigDescriptor = &desc
packOpts.PackImageManifest = true
}
if opts.ManifestMediaType == ocispec.MediaTypeImageManifest {
packOpts.PackImageManifest = true
}
descs, err := loadFiles(ctx, store, annotations, opts.FileRefs, opts.Verbose)
if err != nil {
return err
Expand Down Expand Up @@ -175,7 +186,7 @@ func runPush(opts pushOptions) error {
}

// Push
root, err := pushArtifact(dst, pack, &packOpts, copy, &copyOptions.CopyGraphOptions, opts.Verbose)
root, err := pushArtifact(dst, pack, &packOpts, copy, &copyOptions.CopyGraphOptions, opts.ManifestMediaType == "", opts.Verbose)
if err != nil {
return err
}
Expand Down Expand Up @@ -218,7 +229,7 @@ func updateDisplayOption(opts *oras.CopyGraphOptions, store content.Fetcher, ver
type packFunc func() (ocispec.Descriptor, error)
type copyFunc func(desc ocispec.Descriptor) error

func pushArtifact(dst *remote.Repository, pack packFunc, packOpts *oras.PackOptions, copy copyFunc, copyOpts *oras.CopyGraphOptions, verbose bool) (ocispec.Descriptor, error) {
func pushArtifact(dst *remote.Repository, pack packFunc, packOpts *oras.PackOptions, copy copyFunc, copyOpts *oras.CopyGraphOptions, allowFallback bool, verbose bool) (ocispec.Descriptor, error) {
root, err := pack()
if err != nil {
return ocispec.Descriptor{}, err
Expand All @@ -243,7 +254,7 @@ func pushArtifact(dst *remote.Repository, pack packFunc, packOpts *oras.PackOpti
return root, nil
}

if !copyRootAttempted || root.MediaType != ocispec.MediaTypeArtifactManifest ||
if !allowFallback || !copyRootAttempted || root.MediaType != ocispec.MediaTypeArtifactManifest ||
!isManifestUnsupported(err) {
return ocispec.Descriptor{}, err
}
Expand Down

0 comments on commit eda5207

Please sign in to comment.