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 compatibility mode for push and attach cmd #741

Merged
merged 21 commits into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from 15 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
16 changes: 15 additions & 1 deletion 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 @@ -51,6 +53,14 @@ func attachCmd() *cobra.Command {
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" with specific manifest type for packing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest always splitting the sample commands into multiple examples. If you have more than two options under a flag and put all of those sample commands under a flag example, the layout looks too verbose.

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

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 == option.MediaTypeAutoManifest, opts.Verbose)
if err != nil {
return err
}
Expand Down
82 changes: 82 additions & 0 deletions cmd/oras/internal/option/spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
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"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"

"github.com/spf13/pflag"
qweeah marked this conversation as resolved.
Show resolved Hide resolved
)

const MediaTypeAutoManifest = ""
qweeah marked this conversation as resolved.
Show resolved Hide resolved

// ImageSpec option struct.
type ImageSpec struct {
// Manifest type for building artifact
ManifestMediaType string
// specFlag should be provided in form of `<version>-<manifest type>`
qweeah marked this conversation as resolved.
Show resolved Hide resolved
specFlag string
}

// Parse parses flags into the option.
func (opts *ImageSpec) Parse() error {
switch opts.specFlag {
case "":
opts.ManifestMediaType = MediaTypeAutoManifest
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-api", "v1.1-referrers-tag":
isApi := opts.specFlag == "v1.1-referrers-api"
opts.ReferrersAPI = &isApi
qweeah marked this conversation as resolved.
Show resolved Hide resolved
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" and enforce packed manifest type:
qweeah marked this conversation as resolved.
Show resolved Hide resolved
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
qweeah marked this conversation as resolved.
Show resolved Hide resolved

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 pack an OCI artifact with manifest config at the same time")
qweeah marked this conversation as resolved.
Show resolved Hide resolved
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It reminds me that there will be a feature request eventually to support --config for oras attach.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? The content of config doesn't matter and the media type of config can be deducted from artifact type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they could, they will ask.

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 == option.MediaTypeAutoManifest, 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