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 10 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
17 changes: 16 additions & 1 deletion cmd/oras/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ import (
"oras.land/oras-go/v2/content/file"
oerrors "oras.land/oras/cmd/oras/internal/errors"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/registry"
)

type attachOptions struct {
option.Common
option.Remote
option.Packer
option.ImageSpec
option.DistributionSpec

targetRef string
artifactType string
Expand All @@ -51,6 +54,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" and enforce packed manifest type:
qweeah marked this conversation as resolved.
Show resolved Hide resolved
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
qweeah marked this conversation as resolved.
Show resolved Hide resolved

Example - Attach file "hi.txt" and specify referrers will be discovered:
qweeah marked this conversation as resolved.
Show resolved Hide resolved
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 +114,9 @@ func runAttach(opts attachOptions) error {
if dst.Reference.Reference == "" {
return oerrors.NewErrInvalidReference(dst.Reference)
}
if opts.ReferrersApiSupportState != registry.ReferrersApiSupportUnknown {
dst.SetReferrersCapability(opts.ReferrersApiSupportState == registry.ReferrersApiSupported)
}
subject, err := dst.Resolve(ctx, dst.Reference.Reference)
if err != nil {
return err
Expand All @@ -116,6 +130,7 @@ func runAttach(opts attachOptions) error {
packOpts := oras.PackOptions{
Subject: &subject,
ManifestAnnotations: annotations[option.AnnotationManifest],
PackImageManifest: opts.ManifestSupportState == registry.OCIImage,
}
pack := func() (ocispec.Descriptor, error) {
return oras.Pack(ctx, store, opts.artifactType, descs, packOpts)
Expand All @@ -137,7 +152,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.ManifestSupportState != registry.ManifestSupportUnknown, opts.Verbose)
if err != nil {
return err
}
Expand Down
79 changes: 79 additions & 0 deletions cmd/oras/internal/option/spec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
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"
"oras.land/oras/internal/registry"
)

// ImageSpec option struct.
type ImageSpec struct {
ManifestSupportState registry.ManifestSupportState
qweeah marked this conversation as resolved.
Show resolved Hide resolved

// 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.ManifestSupportState = registry.ManifestSupportUnknown
case "v1.1-image":
opts.ManifestSupportState = registry.OCIImage
case "v1.1-artifact":
opts.ManifestSupportState = registry.OCIArtifact
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", "", "set OCI image spec version and request manifest type. E.g. `v1.1-image,v1.1-artifact`")
qweeah marked this conversation as resolved.
Show resolved Hide resolved
}

// DistributionSpec option struct.
type DistributionSpec struct {
ReferrersApiSupportState registry.ReferrersApiSupportState
qweeah marked this conversation as resolved.
Show resolved Hide resolved

// should be provided in form of`<version>-<api>-<option>`
qweeah marked this conversation as resolved.
Show resolved Hide resolved
specFlag string
}

// Parse parses flags into the option.
func (opts *DistributionSpec) Parse() error {
switch opts.specFlag {
case "":
opts.ReferrersApiSupportState = registry.ReferrersApiSupportUnknown
case "v1.1-referrers-api":
opts.ReferrersApiSupportState = registry.ReferrersApiUnsupported
case "v1.1-referrers-tag":
opts.ReferrersApiSupportState = registry.ReferrersApiSupported
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. E.g. `v1.1-referrers-api,v1.1-referrers-tag`")
qweeah marked this conversation as resolved.
Show resolved Hide resolved
}
18 changes: 15 additions & 3 deletions cmd/oras/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,14 @@ import (
"oras.land/oras/cmd/oras/internal/display"
"oras.land/oras/cmd/oras/internal/fileref"
"oras.land/oras/cmd/oras/internal/option"
"oras.land/oras/internal/registry"
)

type pushOptions struct {
option.Common
option.Remote
option.Packer
option.ImageSpec

targetRef string
extraRefs []string
Expand Down Expand Up @@ -73,6 +75,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 +105,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.ManifestSupportState == registry.OCIArtifact && 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
}
refs := strings.Split(args[0], ",")
opts.targetRef = refs[0]
opts.extraRefs = refs[1:]
Expand Down Expand Up @@ -142,6 +151,9 @@ func runPush(opts pushOptions) error {
packOpts.ConfigDescriptor = &desc
packOpts.PackImageManifest = true
}
if opts.ManifestSupportState == registry.OCIImage {
packOpts.PackImageManifest = true
}
descs, err := loadFiles(ctx, store, annotations, opts.FileRefs, opts.Verbose)
if err != nil {
return err
Expand Down Expand Up @@ -175,7 +187,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.ManifestSupportState != registry.ManifestSupportUnknown, opts.Verbose)
if err != nil {
return err
}
Expand Down Expand Up @@ -218,7 +230,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, noFallback bool, verbose bool) (ocispec.Descriptor, error) {
qweeah marked this conversation as resolved.
Show resolved Hide resolved
root, err := pack()
if err != nil {
return ocispec.Descriptor{}, err
Expand All @@ -243,7 +255,7 @@ func pushArtifact(dst *remote.Repository, pack packFunc, packOpts *oras.PackOpti
return root, nil
}

if !copyRootAttempted || root.MediaType != ocispec.MediaTypeArtifactManifest ||
if noFallback || !copyRootAttempted || root.MediaType != ocispec.MediaTypeArtifactManifest ||
qweeah marked this conversation as resolved.
Show resolved Hide resolved
!isManifestUnsupported(err) {
return ocispec.Descriptor{}, err
}
Expand Down
43 changes: 43 additions & 0 deletions internal/registry/compatibility.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
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 registry

// ManifestSupportState represents the state of remote registry support for
// manifest type.
type ManifestSupportState = int32

const (
// ManifestSupportUnknown represents an unknown state of Referrers API.
ManifestSupportUnknown ManifestSupportState = iota
// OCIArtifact means that the remote registry is known to support oci artifact manifest type.
OCIArtifact
// OCIImage means that the remote registry is known to
// support oci image manifest type only.
OCIImage
)

// ReferrersApiSupportState represents the state of remote registry support for
// referrers API.
type ReferrersApiSupportState = int32

const (
// ReferrersApiSupportUnknown represents an unknown state of Referrers API.
ReferrersApiSupportUnknown ReferrersApiSupportState = iota
// ReferrersApiSupported means that the remote registry is known to supporting referrers API.
ReferrersApiSupported
// ReferrersApiUnsupported means that the remote registry is known to not supporting referrers API.
ReferrersApiUnsupported
)