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: support specifying distribution spec in discover, cp and manifest push commands #757

Merged
merged 10 commits into from
Jan 20, 2023
Merged
Show file tree
Hide file tree
Changes from 9 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: 2 additions & 15 deletions cmd/oras/attach.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"context"
"errors"
"fmt"
"reflect"
"strings"

ocispec "github.com/opencontainers/image-spec/specs-go/v1"
Expand All @@ -28,15 +27,13 @@ import (
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/file"
"oras.land/oras-go/v2/registry/remote"
oerrors "oras.land/oras/cmd/oras/internal/errors"
"oras.land/oras/cmd/oras/internal/option"
)

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

artifactType string
Expand Down Expand Up @@ -85,9 +82,6 @@ Example - Attach file to the manifest tagged 'v1' in an OCI layout folder 'layou
if err := option.Parse(&opts); err != nil {
return err
}
if opts.Type == option.TargetTypeOCILayout && opts.DistributionSpec.ReferrersAPI != nil {
return errors.New("cannot enforce referrers API for image layout target")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -121,15 +115,8 @@ func runAttach(opts attachOptions) error {
if err != nil {
return err
}
if opts.Reference == "" {
return oerrors.NewErrInvalidReferenceStr(opts.Reference)
}
if opts.ReferrersAPI != nil {
if repo, ok := dst.(*remote.Repository); ok {
repo.SetReferrersCapability(*opts.ReferrersAPI)
} else {
return fmt.Errorf("unexpected type %s with target type %s", reflect.TypeOf(dst), opts.Type)
}
if err := opts.EnsureReferenceNotEmpty(); err != nil {
return err
}
subject, err := dst.Resolve(ctx, opts.Reference)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions cmd/oras/cp.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ Example - Upload an artifact from an OCI layout tar archive:
Example - Copy an artifact and its referrers:
oras cp -r localhost:5000/net-monitor:v1 localhost:6000/net-monitor-copy:v1
Example - Copy an artifact and referrers using specific methods for the Referrers API:
oras cp -r --from-distribution-spec v1.1-referrers-api --to-distribution-spec v1.1-referrers-tag \
localhost:5000/net-monitor:v1 localhost:6000/net-monitor-copy:v1
Example - Copy certain platform of an artifact:
oras cp --platform linux/arm/v5 localhost:5000/net-monitor:v1 localhost:6000/net-monitor-copy:v1
Expand All @@ -86,6 +90,7 @@ Example - Copy an artifact with multiple tags with concurrency tuned:
}
cmd.Flags().BoolVarP(&opts.recursive, "recursive", "r", false, "recursively copy the artifact and its referrer artifacts")
cmd.Flags().IntVarP(&opts.concurrency, "concurrency", "", 3, "concurrency level")
opts.EnableDistributionSpecFlag()
option.ApplyFlags(&opts, cmd.Flags())
return cmd
}
Expand Down
7 changes: 7 additions & 0 deletions cmd/oras/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ func discoverCmd() *cobra.Command {
Example - Discover direct referrers of manifest 'hello:v1' in registry 'localhost:5000':
oras discover localhost:5000/hello:v1
Example - Discover direct referrers via referrers API:
oras discover --distribution-spec v1.1-referrers-api localhost:5000/hello:v1
Example - Discover direct referrers via tag scheme:
oras discover --distribution-spec v1.1-referrers-tag localhost:5000/hello:v1
Example - Discover all the referrers of manifest 'hello:v1' in registry 'localhost:5000', displayed in a tree view:
oras discover -o tree localhost:5000/hello:v1
Expand All @@ -77,6 +83,7 @@ Example - Discover referrers with type 'test-artifact' of manifest 'hello:v1' in

cmd.Flags().StringVarP(&opts.artifactType, "artifact-type", "", "", "artifact type")
cmd.Flags().StringVarP(&opts.outputType, "output", "o", "table", "format in which to display referrers (table, json, or tree). tree format will also show indirect referrers")
opts.EnableDistributionSpecFlag()
option.ApplyFlags(&opts, cmd.Flags())
return cmd
}
Expand Down
24 changes: 20 additions & 4 deletions cmd/oras/internal/option/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,15 @@ type Remote struct {
PasswordFromStdin bool
Password string

resolveFlag []string
resolveDialContext func(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error)
resolveFlag []string
resolveDialContext func(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error)
applyDistributionSpec bool
distributionSpec distributionSpec
}

// EnableDistributionSpecFlag set distribution specification flag as applicable.
func (opts *Remote) EnableDistributionSpecFlag() {
opts.applyDistributionSpec = true
}

// ApplyFlags applies flags to a command flag set.
Expand All @@ -57,6 +64,10 @@ func (opts *Remote) ApplyFlags(fs *pflag.FlagSet) {
fs.BoolVarP(&opts.PasswordFromStdin, "password-stdin", "", false, "read password or identity token from stdin")
}

func generatePrefix(prefix, description string) (flagPrefix, notePrefix string) {
return prefix + "-", description + " "
}
Copy link
Contributor

@shizhMSFT shizhMSFT Jan 20, 2023

Choose a reason for hiding this comment

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

Suggested change
func generatePrefix(prefix, description string) (flagPrefix, notePrefix string) {
return prefix + "-", description + " "
}
func applyPrefix(prefix, description string) (flagPrefix, notePrefix string) {
if prefix == "" {
return "", ""
}
return prefix + "-", description + " "
}


// ApplyFlagsWithPrefix applies flags to a command flag set with a prefix string.
// Commonly used for non-unary remote targets.
func (opts *Remote) ApplyFlagsWithPrefix(fs *pflag.FlagSet, prefix, description string) {
Expand All @@ -69,8 +80,10 @@ func (opts *Remote) ApplyFlagsWithPrefix(fs *pflag.FlagSet, prefix, description
if prefix == "" {
shortUser, shortPassword = "u", "p"
} else {
flagPrefix = prefix + "-"
notePrefix = description + " "
flagPrefix, notePrefix = generatePrefix(prefix, description)
}
if opts.applyDistributionSpec {
opts.distributionSpec.ApplyFlagsWithPrefix(fs, prefix, description)
}
fs.StringVarP(&opts.Username, flagPrefix+"username", shortUser, "", notePrefix+"registry username")
fs.StringVarP(&opts.Password, flagPrefix+"password", shortPassword, "", notePrefix+"registry password or identity token")
Expand Down Expand Up @@ -254,6 +267,9 @@ func (opts *Remote) NewRepository(reference string, common Common) (repo *remote
if repo.Client, err = opts.authClient(hostname, common.Debug); err != nil {
return nil, err
}
if opts.distributionSpec.referrersAPI != nil {
repo.SetReferrersCapability(*opts.distributionSpec.referrersAPI)
}
return
}

Expand Down
26 changes: 14 additions & 12 deletions cmd/oras/internal/option/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ package option

import (
"fmt"
"github.com/spf13/pflag"

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

// ImageSpec option struct.
Expand Down Expand Up @@ -50,34 +51,35 @@ 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.
// 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
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 {
func (opts *distributionSpec) Parse() error {
switch opts.specFlag {
case "":
opts.ReferrersAPI = nil
opts.referrersAPI = nil
case "v1.1-referrers-tag":
isApi := false
opts.ReferrersAPI = &isApi
opts.referrersAPI = &isApi
case "v1.1-referrers-api":
isApi := true
opts.ReferrersAPI = &isApi
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")
// ApplyFlagsWithPrefix applies flags to a command flag set with a prefix string.
func (opts *distributionSpec) ApplyFlagsWithPrefix(fs *pflag.FlagSet, prefix, description string) {
flagPrefix, notePrefix := generatePrefix(prefix, description)
fs.StringVar(&opts.specFlag, flagPrefix+"distribution-spec", "", "set OCI distribution spec version and API option for "+notePrefix+"target. options: v1.1-referrers-api, v1.1-referrers-tag")
}
30 changes: 15 additions & 15 deletions cmd/oras/internal/option/target.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package option

import (
"context"
"errors"
"fmt"
"os"
"strings"
Expand All @@ -25,7 +26,7 @@ import (
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content/oci"
"oras.land/oras-go/v2/registry"
"oras.land/oras/cmd/oras/internal/errors"
oerrors "oras.land/oras/cmd/oras/internal/errors"
"oras.land/oras/cmd/oras/internal/fileref"
)

Expand Down Expand Up @@ -67,15 +68,8 @@ func (opts *Target) AnnotatedReference() string {
// Since there is only one target type besides the default `registry` type,
// the full form is not implemented until a new type comes in.
func (opts *Target) applyFlagsWithPrefix(fs *pflag.FlagSet, prefix, description string) {
var (
flagPrefix string
noteSuffix string
)
if prefix != "" {
flagPrefix = prefix + "-"
noteSuffix = description + " "
}
fs.BoolVarP(&opts.isOCILayout, flagPrefix+"oci-layout", "", false, "Set "+noteSuffix+"target as an OCI image layout.")
flagPrefix, notePrefix := generatePrefix(prefix, description)
fs.BoolVarP(&opts.isOCILayout, flagPrefix+"oci-layout", "", false, "Set "+notePrefix+"target as an OCI image layout.")
}

// ApplyFlagsWithPrefix applies flags to a command flag set with a prefix string.
Expand All @@ -90,6 +84,9 @@ func (opts *Target) Parse() error {
switch {
case opts.isOCILayout:
opts.Type = TargetTypeOCILayout
if opts.Remote.distributionSpec.referrersAPI != nil {
return errors.New("cannot enforce referrers API for image layout target")
}
default:
opts.Type = TargetTypeRemote
}
Expand Down Expand Up @@ -168,7 +165,7 @@ func (opts *Target) NewReadonlyTarget(ctx context.Context, common Common) (ReadO
// EnsureReferenceNotEmpty ensures whether the tag or digest is empty.
func (opts *Target) EnsureReferenceNotEmpty() error {
if opts.Reference == "" {
return errors.NewErrInvalidReferenceStr(opts.RawReference)
return oerrors.NewErrInvalidReferenceStr(opts.RawReference)
}
return nil
}
Expand All @@ -180,6 +177,12 @@ type BinaryTarget struct {
To Target
}

// EnableDistributionSpecFlag set distribution specification flag as applicable.
func (opts *BinaryTarget) EnableDistributionSpecFlag() {
opts.From.EnableDistributionSpecFlag()
opts.To.EnableDistributionSpecFlag()
}

// ApplyFlags applies flags to a command flag set fs.
func (opts *BinaryTarget) ApplyFlags(fs *pflag.FlagSet) {
opts.From.ApplyFlagsWithPrefix(fs, "from", "source")
Expand All @@ -188,8 +191,5 @@ func (opts *BinaryTarget) ApplyFlags(fs *pflag.FlagSet) {

// Parse parses user-provided flags and arguments into option struct.
func (opts *BinaryTarget) Parse() error {
if err := opts.From.Parse(); err != nil {
return err
}
return opts.To.Parse()
return Parse(opts)
}
5 changes: 5 additions & 0 deletions cmd/oras/manifest/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ func pushCmd() *cobra.Command {
Example - Push a manifest to repository 'localhost:5000/hello' and tag with 'v1':
oras manifest push localhost:5000/hello:v1 manifest.json
Example - Push a manifest using a specific method for the Referrers API:
oras manifest push --distribution-spec v1.1-referrers-api localhost:5000/hello:v1 manifest.json
oras manifest push --distribution-spec v1.1-referrers-tag localhost:5000/hello:v1 manifest.json
Example - Push a manifest with content read from stdin:
oras manifest push localhost:5000/hello:v1 -
Expand Down Expand Up @@ -94,6 +98,7 @@ Example - Push a manifest to an OCI layout folder 'layout-dir' and tag with 'v1'
},
}

opts.EnableDistributionSpecFlag()
option.ApplyFlags(&opts, cmd.Flags())
cmd.Flags().StringVarP(&opts.mediaType, "media-type", "", "", "media type of manifest")
cmd.Flags().IntVarP(&opts.concurrency, "concurrency", "", 5, "concurrency level")
Expand Down