mirror of
https://github.com/containers/skopeo.git
synced 2025-06-26 14:52:36 +00:00
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:
parent
bc39e4f9b6
commit
8ee3ead743
@ -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)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
@ -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)",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
@ -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
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
|
||||||
}
|
|
||||||
|
@ -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,
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user