Create an "options" structure for each command

This is a big diff, but it really only replaces a few global variables
with functions returning a structure.

The ultimate goal of this patch set is to replace option handling using

> cli.StringFlag{Name:"foo", ...}
> ...
> func somethingHandler(c *cli.Context) error {
>     c.String("foo")
> }

where the declaration and usage are connected only using a string constant,
and it's difficult to notice that one or the other is missing or that the
types don't match, by

> struct somethingOptions {
>    foo string
> }
> ...
> cli.StringFlag{Name:"foo", Destination:&foo}
> ...
> func (opts *somethingOptions) run(c *cli.Context) error {
>     opts.foo
> }

As a first step, this commit ONLY introduces the *Options structures,
but for now empty; nothing changes in the existing implementations.

So, we go from

> func somethingHandler(c *cli.Context error {...}
>
> var somethingCmd = cli.Command {
>     ...
>     Action: somethingHandler
> }

to

> type somethingOptions struct{
> } // empty for now
>
> func somethingCmd() cli.Command {
>     opts := somethingOptions{}
>     return cli.Command {
>         ... // unchanged
>         Action: opts.run
>     }
> }
>
> func (opts *somethingOptions) run(c *cli.context) error {...} // unchanged

Using the struct type has also made it possible to place the definition of
cli.Command in front of the actual command handler, so do that for better
readability.

In a few cases this also broke out an in-line lambda in the Action: field
into a separate opts.run method.  Again, nothing else has changed.

Signed-off-by: Miloslav Trmač <mitr@redhat.com>
This commit is contained in:
Miloslav Trmač 2018-07-07 01:40:21 +02:00
parent bc39e4f9b6
commit 8ee3ead743
7 changed files with 429 additions and 377 deletions

View File

@ -31,7 +31,109 @@ func contextsFromGlobalOptions(c *cli.Context) (*types.SystemContext, *types.Sys
return sourceCtx, destinationCtx, nil return sourceCtx, destinationCtx, nil
} }
func copyHandler(c *cli.Context) error { type copyOptions struct {
}
func copyCmd() cli.Command {
opts := copyOptions{}
return cli.Command{
Name: "copy",
Usage: "Copy an IMAGE-NAME from one location to another",
Description: fmt.Sprintf(`
Container "IMAGE-NAME" uses a "transport":"details" format.
Supported transports:
%s
See skopeo(1) section "IMAGE NAMES" for the expected format
`, strings.Join(transports.ListNames(), ", ")),
ArgsUsage: "SOURCE-IMAGE DESTINATION-IMAGE",
Action: opts.run,
// FIXME: Do we need to namespace the GPG aspect?
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "additional-tag",
Usage: "additional tags (supports docker-archive)",
},
cli.StringFlag{
Name: "authfile",
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
},
cli.BoolFlag{
Name: "remove-signatures",
Usage: "Do not copy signatures from SOURCE-IMAGE",
},
cli.StringFlag{
Name: "sign-by",
Usage: "Sign the image using a GPG key with the specified `FINGERPRINT`",
},
cli.StringFlag{
Name: "src-creds, screds",
Value: "",
Usage: "Use `USERNAME[:PASSWORD]` for accessing the source registry",
},
cli.StringFlag{
Name: "dest-creds, dcreds",
Value: "",
Usage: "Use `USERNAME[:PASSWORD]` for accessing the destination registry",
},
cli.StringFlag{
Name: "src-cert-dir",
Value: "",
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the source registry or daemon",
},
cli.BoolTFlag{
Name: "src-tls-verify",
Usage: "require HTTPS and verify certificates when talking to the container source registry or daemon (defaults to true)",
},
cli.StringFlag{
Name: "dest-cert-dir",
Value: "",
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the destination registry or daemon",
},
cli.BoolTFlag{
Name: "dest-tls-verify",
Usage: "require HTTPS and verify certificates when talking to the container destination registry or daemon (defaults to true)",
},
cli.StringFlag{
Name: "dest-ostree-tmp-dir",
Value: "",
Usage: "`DIRECTORY` to use for OSTree temporary files",
},
cli.StringFlag{
Name: "src-shared-blob-dir",
Value: "",
Usage: "`DIRECTORY` to use to fetch retrieved blobs (OCI layout sources only)",
},
cli.StringFlag{
Name: "dest-shared-blob-dir",
Value: "",
Usage: "`DIRECTORY` to use to store retrieved blobs (OCI layout destinations only)",
},
cli.StringFlag{
Name: "format, f",
Usage: "`MANIFEST TYPE` (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)",
},
cli.BoolFlag{
Name: "dest-compress",
Usage: "Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)",
},
cli.StringFlag{
Name: "src-daemon-host",
Value: "",
Usage: "use docker daemon host at `HOST` (docker-daemon sources only)",
},
cli.StringFlag{
Name: "dest-daemon-host",
Value: "",
Usage: "use docker daemon host at `HOST` (docker-daemon destinations only)",
},
},
}
}
func (opts *copyOptions) run(c *cli.Context) error {
if len(c.Args()) != 2 { if len(c.Args()) != 2 {
cli.ShowCommandHelp(c, "copy") cli.ShowCommandHelp(c, "copy")
return errors.New("Exactly two arguments expected") return errors.New("Exactly two arguments expected")
@ -100,99 +202,3 @@ func copyHandler(c *cli.Context) error {
}) })
return err return err
} }
var copyCmd = cli.Command{
Name: "copy",
Usage: "Copy an IMAGE-NAME from one location to another",
Description: fmt.Sprintf(`
Container "IMAGE-NAME" uses a "transport":"details" format.
Supported transports:
%s
See skopeo(1) section "IMAGE NAMES" for the expected format
`, strings.Join(transports.ListNames(), ", ")),
ArgsUsage: "SOURCE-IMAGE DESTINATION-IMAGE",
Action: copyHandler,
// FIXME: Do we need to namespace the GPG aspect?
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "additional-tag",
Usage: "additional tags (supports docker-archive)",
},
cli.StringFlag{
Name: "authfile",
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
},
cli.BoolFlag{
Name: "remove-signatures",
Usage: "Do not copy signatures from SOURCE-IMAGE",
},
cli.StringFlag{
Name: "sign-by",
Usage: "Sign the image using a GPG key with the specified `FINGERPRINT`",
},
cli.StringFlag{
Name: "src-creds, screds",
Value: "",
Usage: "Use `USERNAME[:PASSWORD]` for accessing the source registry",
},
cli.StringFlag{
Name: "dest-creds, dcreds",
Value: "",
Usage: "Use `USERNAME[:PASSWORD]` for accessing the destination registry",
},
cli.StringFlag{
Name: "src-cert-dir",
Value: "",
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the source registry or daemon",
},
cli.BoolTFlag{
Name: "src-tls-verify",
Usage: "require HTTPS and verify certificates when talking to the container source registry or daemon (defaults to true)",
},
cli.StringFlag{
Name: "dest-cert-dir",
Value: "",
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the destination registry or daemon",
},
cli.BoolTFlag{
Name: "dest-tls-verify",
Usage: "require HTTPS and verify certificates when talking to the container destination registry or daemon (defaults to true)",
},
cli.StringFlag{
Name: "dest-ostree-tmp-dir",
Value: "",
Usage: "`DIRECTORY` to use for OSTree temporary files",
},
cli.StringFlag{
Name: "src-shared-blob-dir",
Value: "",
Usage: "`DIRECTORY` to use to fetch retrieved blobs (OCI layout sources only)",
},
cli.StringFlag{
Name: "dest-shared-blob-dir",
Value: "",
Usage: "`DIRECTORY` to use to store retrieved blobs (OCI layout destinations only)",
},
cli.StringFlag{
Name: "format, f",
Usage: "`MANIFEST TYPE` (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)",
},
cli.BoolFlag{
Name: "dest-compress",
Usage: "Compress tarball image layers when saving to directory using the 'dir' transport. (default is same compression type as source)",
},
cli.StringFlag{
Name: "src-daemon-host",
Value: "",
Usage: "use docker daemon host at `HOST` (docker-daemon sources only)",
},
cli.StringFlag{
Name: "dest-daemon-host",
Value: "",
Usage: "use docker daemon host at `HOST` (docker-daemon destinations only)",
},
},
}

View File

@ -10,7 +10,48 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
func deleteHandler(c *cli.Context) error { type deleteOptions struct {
}
func deleteCmd() cli.Command {
opts := deleteOptions{}
return cli.Command{
Name: "delete",
Usage: "Delete image IMAGE-NAME",
Description: fmt.Sprintf(`
Delete an "IMAGE_NAME" from a transport
Supported transports:
%s
See skopeo(1) section "IMAGE NAMES" for the expected format
`, strings.Join(transports.ListNames(), ", ")),
ArgsUsage: "IMAGE-NAME",
Action: opts.run,
Flags: []cli.Flag{
cli.StringFlag{
Name: "authfile",
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
},
cli.StringFlag{
Name: "creds",
Value: "",
Usage: "Use `USERNAME[:PASSWORD]` for accessing the registry",
},
cli.StringFlag{
Name: "cert-dir",
Value: "",
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry",
},
cli.BoolTFlag{
Name: "tls-verify",
Usage: "require HTTPS and verify certificates when talking to container registries (defaults to true)",
},
},
}
}
func (opts *deleteOptions) run(c *cli.Context) error {
if len(c.Args()) != 1 { if len(c.Args()) != 1 {
return errors.New("Usage: delete imageReference") return errors.New("Usage: delete imageReference")
} }
@ -29,38 +70,3 @@ func deleteHandler(c *cli.Context) error {
defer cancel() defer cancel()
return ref.DeleteImage(ctx, sys) return ref.DeleteImage(ctx, sys)
} }
var deleteCmd = cli.Command{
Name: "delete",
Usage: "Delete image IMAGE-NAME",
Description: fmt.Sprintf(`
Delete an "IMAGE_NAME" from a transport
Supported transports:
%s
See skopeo(1) section "IMAGE NAMES" for the expected format
`, strings.Join(transports.ListNames(), ", ")),
ArgsUsage: "IMAGE-NAME",
Action: deleteHandler,
Flags: []cli.Flag{
cli.StringFlag{
Name: "authfile",
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
},
cli.StringFlag{
Name: "creds",
Value: "",
Usage: "Use `USERNAME[:PASSWORD]` for accessing the registry",
},
cli.StringFlag{
Name: "cert-dir",
Value: "",
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry",
},
cli.BoolTFlag{
Name: "tls-verify",
Usage: "require HTTPS and verify certificates when talking to container registries (defaults to true)",
},
},
}

View File

@ -29,10 +29,15 @@ type inspectOutput struct {
Layers []string Layers []string
} }
var inspectCmd = cli.Command{ type inspectOptions struct {
Name: "inspect", }
Usage: "Inspect image IMAGE-NAME",
Description: fmt.Sprintf(` func inspectCmd() cli.Command {
opts := inspectOptions{}
return cli.Command{
Name: "inspect",
Usage: "Inspect image IMAGE-NAME",
Description: fmt.Sprintf(`
Return low-level information about "IMAGE-NAME" in a registry/transport Return low-level information about "IMAGE-NAME" in a registry/transport
Supported transports: Supported transports:
@ -40,101 +45,104 @@ var inspectCmd = cli.Command{
See skopeo(1) section "IMAGE NAMES" for the expected format See skopeo(1) section "IMAGE NAMES" for the expected format
`, strings.Join(transports.ListNames(), ", ")), `, strings.Join(transports.ListNames(), ", ")),
ArgsUsage: "IMAGE-NAME", ArgsUsage: "IMAGE-NAME",
Flags: []cli.Flag{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "authfile", Name: "authfile",
Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json",
},
cli.StringFlag{
Name: "cert-dir",
Value: "",
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry",
},
cli.BoolTFlag{
Name: "tls-verify",
Usage: "require HTTPS and verify certificates when talking to container registries (defaults to true)",
},
cli.BoolFlag{
Name: "raw",
Usage: "output raw manifest",
},
cli.StringFlag{
Name: "creds",
Value: "",
Usage: "Use `USERNAME[:PASSWORD]` for accessing the registry",
},
}, },
cli.StringFlag{ Action: opts.run,
Name: "cert-dir", }
Value: "", }
Usage: "use certificates at `PATH` (*.crt, *.cert, *.key) to connect to the registry",
}, func (opts *inspectOptions) run(c *cli.Context) (retErr error) {
cli.BoolTFlag{ ctx, cancel := commandTimeoutContextFromGlobalOptions(c)
Name: "tls-verify", defer cancel()
Usage: "require HTTPS and verify certificates when talking to container registries (defaults to true)",
}, img, err := parseImage(ctx, c)
cli.BoolFlag{ if err != nil {
Name: "raw", return err
Usage: "output raw manifest", }
},
cli.StringFlag{ defer func() {
Name: "creds", if err := img.Close(); err != nil {
Value: "", retErr = errors.Wrapf(retErr, fmt.Sprintf("(could not close image: %v) ", err))
Usage: "Use `USERNAME[:PASSWORD]` for accessing the registry", }
}, }()
},
Action: func(c *cli.Context) (retErr error) { rawManifest, _, err := img.Manifest(ctx)
ctx, cancel := commandTimeoutContextFromGlobalOptions(c) if err != nil {
defer cancel() return err
}
img, err := parseImage(ctx, c) if c.Bool("raw") {
if err != nil { _, err := c.App.Writer.Write(rawManifest)
return err if err != nil {
} return fmt.Errorf("Error writing manifest to standard output: %v", err)
}
defer func() { return nil
if err := img.Close(); err != nil { }
retErr = errors.Wrapf(retErr, fmt.Sprintf("(could not close image: %v) ", err)) imgInspect, err := img.Inspect(ctx)
} if err != nil {
}() return err
}
rawManifest, _, err := img.Manifest(ctx) outputData := inspectOutput{
if err != nil { Name: "", // Set below if DockerReference() is known
return err Tag: imgInspect.Tag,
} // Digest is set below.
if c.Bool("raw") { RepoTags: []string{}, // Possibly overriden for docker.Transport.
_, err := c.App.Writer.Write(rawManifest) Created: imgInspect.Created,
if err != nil { DockerVersion: imgInspect.DockerVersion,
return fmt.Errorf("Error writing manifest to standard output: %v", err) Labels: imgInspect.Labels,
} Architecture: imgInspect.Architecture,
return nil Os: imgInspect.Os,
} Layers: imgInspect.Layers,
imgInspect, err := img.Inspect(ctx) }
if err != nil { outputData.Digest, err = manifest.Digest(rawManifest)
return err if err != nil {
} return fmt.Errorf("Error computing manifest digest: %v", err)
outputData := inspectOutput{ }
Name: "", // Set below if DockerReference() is known if dockerRef := img.Reference().DockerReference(); dockerRef != nil {
Tag: imgInspect.Tag, outputData.Name = dockerRef.Name()
// Digest is set below. }
RepoTags: []string{}, // Possibly overriden for docker.Transport. if img.Reference().Transport() == docker.Transport {
Created: imgInspect.Created, sys, err := contextFromGlobalOptions(c, "")
DockerVersion: imgInspect.DockerVersion, if err != nil {
Labels: imgInspect.Labels, return err
Architecture: imgInspect.Architecture, }
Os: imgInspect.Os, outputData.RepoTags, err = docker.GetRepositoryTags(ctx, sys, img.Reference())
Layers: imgInspect.Layers, if err != nil {
} // some registries may decide to block the "list all tags" endpoint
outputData.Digest, err = manifest.Digest(rawManifest) // gracefully allow the inspect to continue in this case. Currently
if err != nil { // the IBM Bluemix container registry has this restriction.
return fmt.Errorf("Error computing manifest digest: %v", err) if !strings.Contains(err.Error(), "401") {
} return fmt.Errorf("Error determining repository tags: %v", err)
if dockerRef := img.Reference().DockerReference(); dockerRef != nil { }
outputData.Name = dockerRef.Name() logrus.Warnf("Registry disallows tag list retrieval; skipping")
} }
if img.Reference().Transport() == docker.Transport { }
sys, err := contextFromGlobalOptions(c, "") out, err := json.MarshalIndent(outputData, "", " ")
if err != nil { if err != nil {
return err return err
} }
outputData.RepoTags, err = docker.GetRepositoryTags(ctx, sys, img.Reference()) fmt.Fprintln(c.App.Writer, string(out))
if err != nil { return nil
// some registries may decide to block the "list all tags" endpoint
// gracefully allow the inspect to continue in this case. Currently
// the IBM Bluemix container registry has this restriction.
if !strings.Contains(err.Error(), "401") {
return fmt.Errorf("Error determining repository tags: %v", err)
}
logrus.Warnf("Registry disallows tag list retrieval; skipping")
}
}
out, err := json.MarshalIndent(outputData, "", " ")
if err != nil {
return err
}
fmt.Fprintln(c.App.Writer, string(out))
return nil
},
} }

View File

@ -15,114 +15,122 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
var layersCmd = cli.Command{ type layersOptions struct {
Name: "layers", }
Usage: "Get layers of IMAGE-NAME",
ArgsUsage: "IMAGE-NAME [LAYER...]", func layersCmd() cli.Command {
Hidden: true, opts := &layersOptions{}
Action: func(c *cli.Context) (retErr error) { return cli.Command{
fmt.Fprintln(os.Stderr, `DEPRECATED: skopeo layers is deprecated in favor of skopeo copy`) Name: "layers",
if c.NArg() == 0 { Usage: "Get layers of IMAGE-NAME",
return errors.New("Usage: layers imageReference [layer...]") ArgsUsage: "IMAGE-NAME [LAYER...]",
Hidden: true,
Action: opts.run,
}
}
func (opts *layersOptions) run(c *cli.Context) (retErr error) {
fmt.Fprintln(os.Stderr, `DEPRECATED: skopeo layers is deprecated in favor of skopeo copy`)
if c.NArg() == 0 {
return errors.New("Usage: layers imageReference [layer...]")
}
ctx, cancel := commandTimeoutContextFromGlobalOptions(c)
defer cancel()
sys, err := contextFromGlobalOptions(c, "")
if err != nil {
return err
}
cache := blobinfocache.DefaultCache(sys)
rawSource, err := parseImageSource(ctx, c, c.Args()[0])
if err != nil {
return err
}
src, err := image.FromSource(ctx, sys, rawSource)
if err != nil {
if closeErr := rawSource.Close(); closeErr != nil {
return errors.Wrapf(err, " (close error: %v)", closeErr)
} }
ctx, cancel := commandTimeoutContextFromGlobalOptions(c) return err
defer cancel() }
defer func() {
if err := src.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (close error: %v)", err)
}
}()
sys, err := contextFromGlobalOptions(c, "") type blobDigest struct {
digest digest.Digest
isConfig bool
}
var blobDigests []blobDigest
for _, dString := range c.Args().Tail() {
if !strings.HasPrefix(dString, "sha256:") {
dString = "sha256:" + dString
}
d, err := digest.Parse(dString)
if err != nil { if err != nil {
return err return err
} }
cache := blobinfocache.DefaultCache(sys) blobDigests = append(blobDigests, blobDigest{digest: d, isConfig: false})
rawSource, err := parseImageSource(ctx, c, c.Args()[0]) }
if len(blobDigests) == 0 {
layers := src.LayerInfos()
seenLayers := map[digest.Digest]struct{}{}
for _, info := range layers {
if _, ok := seenLayers[info.Digest]; !ok {
blobDigests = append(blobDigests, blobDigest{digest: info.Digest, isConfig: false})
seenLayers[info.Digest] = struct{}{}
}
}
configInfo := src.ConfigInfo()
if configInfo.Digest != "" {
blobDigests = append(blobDigests, blobDigest{digest: configInfo.Digest, isConfig: true})
}
}
tmpDir, err := ioutil.TempDir(".", "layers-")
if err != nil {
return err
}
tmpDirRef, err := directory.NewReference(tmpDir)
if err != nil {
return err
}
dest, err := tmpDirRef.NewImageDestination(ctx, nil)
if err != nil {
return err
}
defer func() {
if err := dest.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (close error: %v)", err)
}
}()
for _, bd := range blobDigests {
r, blobSize, err := rawSource.GetBlob(ctx, types.BlobInfo{Digest: bd.digest, Size: -1}, cache)
if err != nil { if err != nil {
return err return err
} }
src, err := image.FromSource(ctx, sys, rawSource) if _, err := dest.PutBlob(ctx, r, types.BlobInfo{Digest: bd.digest, Size: blobSize}, cache, bd.isConfig); err != nil {
if err != nil { if closeErr := r.Close(); closeErr != nil {
if closeErr := rawSource.Close(); closeErr != nil {
return errors.Wrapf(err, " (close error: %v)", closeErr) return errors.Wrapf(err, " (close error: %v)", closeErr)
} }
return err return err
} }
defer func() { }
if err := src.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (close error: %v)", err)
}
}()
type blobDigest struct { manifest, _, err := src.Manifest(ctx)
digest digest.Digest if err != nil {
isConfig bool return err
} }
var blobDigests []blobDigest if err := dest.PutManifest(ctx, manifest); err != nil {
for _, dString := range c.Args().Tail() { return err
if !strings.HasPrefix(dString, "sha256:") { }
dString = "sha256:" + dString
}
d, err := digest.Parse(dString)
if err != nil {
return err
}
blobDigests = append(blobDigests, blobDigest{digest: d, isConfig: false})
}
if len(blobDigests) == 0 { return dest.Commit(ctx)
layers := src.LayerInfos()
seenLayers := map[digest.Digest]struct{}{}
for _, info := range layers {
if _, ok := seenLayers[info.Digest]; !ok {
blobDigests = append(blobDigests, blobDigest{digest: info.Digest, isConfig: false})
seenLayers[info.Digest] = struct{}{}
}
}
configInfo := src.ConfigInfo()
if configInfo.Digest != "" {
blobDigests = append(blobDigests, blobDigest{digest: configInfo.Digest, isConfig: true})
}
}
tmpDir, err := ioutil.TempDir(".", "layers-")
if err != nil {
return err
}
tmpDirRef, err := directory.NewReference(tmpDir)
if err != nil {
return err
}
dest, err := tmpDirRef.NewImageDestination(ctx, nil)
if err != nil {
return err
}
defer func() {
if err := dest.Close(); err != nil {
retErr = errors.Wrapf(retErr, " (close error: %v)", err)
}
}()
for _, bd := range blobDigests {
r, blobSize, err := rawSource.GetBlob(ctx, types.BlobInfo{Digest: bd.digest, Size: -1}, cache)
if err != nil {
return err
}
if _, err := dest.PutBlob(ctx, r, types.BlobInfo{Digest: bd.digest, Size: blobSize}, cache, bd.isConfig); err != nil {
if closeErr := r.Close(); closeErr != nil {
return errors.Wrapf(err, " (close error: %v)", closeErr)
}
return err
}
}
manifest, _, err := src.Manifest(ctx)
if err != nil {
return err
}
if err := dest.PutManifest(ctx, manifest); err != nil {
return err
}
return dest.Commit(ctx)
},
} }

View File

@ -75,14 +75,14 @@ func createApp() *cli.App {
return nil return nil
} }
app.Commands = []cli.Command{ app.Commands = []cli.Command{
copyCmd, copyCmd(),
inspectCmd, inspectCmd(),
layersCmd, layersCmd(),
deleteCmd, deleteCmd(),
manifestDigestCmd, manifestDigestCmd(),
standaloneSignCmd, standaloneSignCmd(),
standaloneVerifyCmd, standaloneVerifyCmd(),
untrustedSignatureDumpCmd, untrustedSignatureDumpCmd(),
} }
return app return app
} }

View File

@ -9,7 +9,20 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
func manifestDigest(context *cli.Context) error { type manifestDigestOptions struct {
}
func manifestDigestCmd() cli.Command {
opts := manifestDigestOptions{}
return cli.Command{
Name: "manifest-digest",
Usage: "Compute a manifest digest of a file",
ArgsUsage: "MANIFEST",
Action: opts.run,
}
}
func (opts *manifestDigestOptions) run(context *cli.Context) error {
if len(context.Args()) != 1 { if len(context.Args()) != 1 {
return errors.New("Usage: skopeo manifest-digest manifest") return errors.New("Usage: skopeo manifest-digest manifest")
} }
@ -26,10 +39,3 @@ func manifestDigest(context *cli.Context) error {
fmt.Fprintf(context.App.Writer, "%s\n", digest) fmt.Fprintf(context.App.Writer, "%s\n", digest)
return nil return nil
} }
var manifestDigestCmd = cli.Command{
Name: "manifest-digest",
Usage: "Compute a manifest digest of a file",
ArgsUsage: "MANIFEST",
Action: manifestDigest,
}

View File

@ -10,7 +10,26 @@ import (
"github.com/urfave/cli" "github.com/urfave/cli"
) )
func standaloneSign(c *cli.Context) error { type standaloneSignOptions struct {
}
func standaloneSignCmd() cli.Command {
opts := standaloneSignOptions{}
return cli.Command{
Name: "standalone-sign",
Usage: "Create a signature using local files",
ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT",
Action: opts.run,
Flags: []cli.Flag{
cli.StringFlag{
Name: "output, o",
Usage: "output the signature to `SIGNATURE`",
},
},
}
}
func (opts *standaloneSignOptions) run(c *cli.Context) error {
outputFile := c.String("output") outputFile := c.String("output")
if len(c.Args()) != 3 || outputFile == "" { if len(c.Args()) != 3 || outputFile == "" {
return errors.New("Usage: skopeo standalone-sign manifest docker-reference key-fingerprint -o signature") return errors.New("Usage: skopeo standalone-sign manifest docker-reference key-fingerprint -o signature")
@ -40,20 +59,20 @@ func standaloneSign(c *cli.Context) error {
return nil return nil
} }
var standaloneSignCmd = cli.Command{ type standaloneVerifyOptions struct {
Name: "standalone-sign",
Usage: "Create a signature using local files",
ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT",
Action: standaloneSign,
Flags: []cli.Flag{
cli.StringFlag{
Name: "output, o",
Usage: "output the signature to `SIGNATURE`",
},
},
} }
func standaloneVerify(c *cli.Context) error { func standaloneVerifyCmd() cli.Command {
opts := standaloneVerifyOptions{}
return cli.Command{
Name: "standalone-verify",
Usage: "Verify a signature using local files",
ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT SIGNATURE",
Action: opts.run,
}
}
func (opts *standaloneVerifyOptions) run(c *cli.Context) error {
if len(c.Args()) != 4 { if len(c.Args()) != 4 {
return errors.New("Usage: skopeo standalone-verify manifest docker-reference key-fingerprint signature") return errors.New("Usage: skopeo standalone-verify manifest docker-reference key-fingerprint signature")
} }
@ -85,14 +104,27 @@ func standaloneVerify(c *cli.Context) error {
return nil return nil
} }
var standaloneVerifyCmd = cli.Command{ // WARNING: Do not use the contents of this for ANY security decisions,
Name: "standalone-verify", // and be VERY CAREFUL about showing this information to humans in any way which suggest that these values “are probably” reliable.
Usage: "Verify a signature using local files", // There is NO REASON to expect the values to be correct, or not intentionally misleading
ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT SIGNATURE", // (including things like “✅ Verified by $authority”)
Action: standaloneVerify, //
// The subcommand is undocumented, and it may be renamed or entirely disappear in the future.
type untrustedSignatureDumpOptions struct {
} }
func untrustedSignatureDump(c *cli.Context) error { func untrustedSignatureDumpCmd() cli.Command {
opts := untrustedSignatureDumpOptions{}
return cli.Command{
Name: "untrusted-signature-dump-without-verification",
Usage: "Dump contents of a signature WITHOUT VERIFYING IT",
ArgsUsage: "SIGNATURE",
Hidden: true,
Action: opts.run,
}
}
func (opts *untrustedSignatureDumpOptions) run(c *cli.Context) error {
if len(c.Args()) != 1 { if len(c.Args()) != 1 {
return errors.New("Usage: skopeo untrusted-signature-dump-without-verification signature") return errors.New("Usage: skopeo untrusted-signature-dump-without-verification signature")
} }
@ -114,17 +146,3 @@ func untrustedSignatureDump(c *cli.Context) error {
fmt.Fprintln(c.App.Writer, string(untrustedOut)) fmt.Fprintln(c.App.Writer, string(untrustedOut))
return nil return nil
} }
// WARNING: Do not use the contents of this for ANY security decisions,
// and be VERY CAREFUL about showing this information to humans in any way which suggest that these values “are probably” reliable.
// There is NO REASON to expect the values to be correct, or not intentionally misleading
// (including things like “✅ Verified by $authority”)
//
// The subcommand is undocumented, and it may be renamed or entirely disappear in the future.
var untrustedSignatureDumpCmd = cli.Command{
Name: "untrusted-signature-dump-without-verification",
Usage: "Dump contents of a signature WITHOUT VERIFYING IT",
ArgsUsage: "SIGNATURE",
Hidden: true,
Action: untrustedSignatureDump,
}