diff --git a/cmd/skopeo/copy.go b/cmd/skopeo/copy.go index be8e9068..9bffa0af 100644 --- a/cmd/skopeo/copy.go +++ b/cmd/skopeo/copy.go @@ -31,7 +31,109 @@ func contextsFromGlobalOptions(c *cli.Context) (*types.SystemContext, *types.Sys 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 { cli.ShowCommandHelp(c, "copy") return errors.New("Exactly two arguments expected") @@ -100,99 +202,3 @@ func copyHandler(c *cli.Context) error { }) 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)", - }, - }, -} diff --git a/cmd/skopeo/delete.go b/cmd/skopeo/delete.go index 5cc7e203..4479f0a2 100644 --- a/cmd/skopeo/delete.go +++ b/cmd/skopeo/delete.go @@ -10,7 +10,48 @@ import ( "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 { return errors.New("Usage: delete imageReference") } @@ -29,38 +70,3 @@ func deleteHandler(c *cli.Context) error { defer cancel() 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)", - }, - }, -} diff --git a/cmd/skopeo/inspect.go b/cmd/skopeo/inspect.go index 2238e436..ab1b0781 100644 --- a/cmd/skopeo/inspect.go +++ b/cmd/skopeo/inspect.go @@ -29,10 +29,15 @@ type inspectOutput struct { Layers []string } -var inspectCmd = cli.Command{ - Name: "inspect", - Usage: "Inspect image IMAGE-NAME", - Description: fmt.Sprintf(` +type inspectOptions struct { +} + +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 Supported transports: @@ -40,101 +45,104 @@ var inspectCmd = cli.Command{ See skopeo(1) section "IMAGE NAMES" for the expected format `, strings.Join(transports.ListNames(), ", ")), - ArgsUsage: "IMAGE-NAME", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", + ArgsUsage: "IMAGE-NAME", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "authfile", + 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{ - 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", - }, - }, - Action: func(c *cli.Context) (retErr error) { - ctx, cancel := commandTimeoutContextFromGlobalOptions(c) - defer cancel() - - img, err := parseImage(ctx, c) - if err != nil { - return err - } - - defer func() { - if err := img.Close(); err != nil { - retErr = errors.Wrapf(retErr, fmt.Sprintf("(could not close image: %v) ", err)) - } - }() - - rawManifest, _, err := img.Manifest(ctx) - if err != nil { - return err - } - if c.Bool("raw") { - _, err := c.App.Writer.Write(rawManifest) - if err != nil { - return fmt.Errorf("Error writing manifest to standard output: %v", err) - } - return nil - } - imgInspect, err := img.Inspect(ctx) - if err != nil { - return err - } - outputData := inspectOutput{ - Name: "", // Set below if DockerReference() is known - Tag: imgInspect.Tag, - // Digest is set below. - RepoTags: []string{}, // Possibly overriden for docker.Transport. - Created: imgInspect.Created, - DockerVersion: imgInspect.DockerVersion, - Labels: imgInspect.Labels, - Architecture: imgInspect.Architecture, - Os: imgInspect.Os, - Layers: imgInspect.Layers, - } - outputData.Digest, err = manifest.Digest(rawManifest) - if err != nil { - return fmt.Errorf("Error computing manifest digest: %v", err) - } - if dockerRef := img.Reference().DockerReference(); dockerRef != nil { - outputData.Name = dockerRef.Name() - } - if img.Reference().Transport() == docker.Transport { - sys, err := contextFromGlobalOptions(c, "") - if err != nil { - return err - } - outputData.RepoTags, err = docker.GetRepositoryTags(ctx, sys, img.Reference()) - if err != 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 - }, + Action: opts.run, + } +} + +func (opts *inspectOptions) run(c *cli.Context) (retErr error) { + ctx, cancel := commandTimeoutContextFromGlobalOptions(c) + defer cancel() + + img, err := parseImage(ctx, c) + if err != nil { + return err + } + + defer func() { + if err := img.Close(); err != nil { + retErr = errors.Wrapf(retErr, fmt.Sprintf("(could not close image: %v) ", err)) + } + }() + + rawManifest, _, err := img.Manifest(ctx) + if err != nil { + return err + } + if c.Bool("raw") { + _, err := c.App.Writer.Write(rawManifest) + if err != nil { + return fmt.Errorf("Error writing manifest to standard output: %v", err) + } + return nil + } + imgInspect, err := img.Inspect(ctx) + if err != nil { + return err + } + outputData := inspectOutput{ + Name: "", // Set below if DockerReference() is known + Tag: imgInspect.Tag, + // Digest is set below. + RepoTags: []string{}, // Possibly overriden for docker.Transport. + Created: imgInspect.Created, + DockerVersion: imgInspect.DockerVersion, + Labels: imgInspect.Labels, + Architecture: imgInspect.Architecture, + Os: imgInspect.Os, + Layers: imgInspect.Layers, + } + outputData.Digest, err = manifest.Digest(rawManifest) + if err != nil { + return fmt.Errorf("Error computing manifest digest: %v", err) + } + if dockerRef := img.Reference().DockerReference(); dockerRef != nil { + outputData.Name = dockerRef.Name() + } + if img.Reference().Transport() == docker.Transport { + sys, err := contextFromGlobalOptions(c, "") + if err != nil { + return err + } + outputData.RepoTags, err = docker.GetRepositoryTags(ctx, sys, img.Reference()) + if err != 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 } diff --git a/cmd/skopeo/layers.go b/cmd/skopeo/layers.go index ec95c795..a5b76ae6 100644 --- a/cmd/skopeo/layers.go +++ b/cmd/skopeo/layers.go @@ -15,114 +15,122 @@ import ( "github.com/urfave/cli" ) -var layersCmd = cli.Command{ - Name: "layers", - Usage: "Get layers of IMAGE-NAME", - ArgsUsage: "IMAGE-NAME [LAYER...]", - Hidden: true, - Action: func(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...]") +type layersOptions struct { +} + +func layersCmd() cli.Command { + opts := &layersOptions{} + return cli.Command{ + Name: "layers", + Usage: "Get layers of IMAGE-NAME", + 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) - defer cancel() + return err + } + 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 { return err } - cache := blobinfocache.DefaultCache(sys) - rawSource, err := parseImageSource(ctx, c, c.Args()[0]) + blobDigests = append(blobDigests, blobDigest{digest: d, isConfig: false}) + } + + 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 { return err } - src, err := image.FromSource(ctx, sys, rawSource) - if err != nil { - if closeErr := rawSource.Close(); closeErr != nil { + 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 } - defer func() { - if err := src.Close(); err != nil { - retErr = errors.Wrapf(retErr, " (close error: %v)", err) - } - }() + } - 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 { - return err - } - blobDigests = append(blobDigests, blobDigest{digest: d, isConfig: false}) - } + manifest, _, err := src.Manifest(ctx) + if err != nil { + return err + } + if err := dest.PutManifest(ctx, manifest); err != nil { + return err + } - 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 { - 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) - }, + return dest.Commit(ctx) } diff --git a/cmd/skopeo/main.go b/cmd/skopeo/main.go index 78fa2af3..6a2fd560 100644 --- a/cmd/skopeo/main.go +++ b/cmd/skopeo/main.go @@ -75,14 +75,14 @@ func createApp() *cli.App { return nil } app.Commands = []cli.Command{ - copyCmd, - inspectCmd, - layersCmd, - deleteCmd, - manifestDigestCmd, - standaloneSignCmd, - standaloneVerifyCmd, - untrustedSignatureDumpCmd, + copyCmd(), + inspectCmd(), + layersCmd(), + deleteCmd(), + manifestDigestCmd(), + standaloneSignCmd(), + standaloneVerifyCmd(), + untrustedSignatureDumpCmd(), } return app } diff --git a/cmd/skopeo/manifest.go b/cmd/skopeo/manifest.go index a04f5e21..177fefe5 100644 --- a/cmd/skopeo/manifest.go +++ b/cmd/skopeo/manifest.go @@ -9,7 +9,20 @@ import ( "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 { 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) return nil } - -var manifestDigestCmd = cli.Command{ - Name: "manifest-digest", - Usage: "Compute a manifest digest of a file", - ArgsUsage: "MANIFEST", - Action: manifestDigest, -} diff --git a/cmd/skopeo/signing.go b/cmd/skopeo/signing.go index 500fedd5..fd0332e3 100644 --- a/cmd/skopeo/signing.go +++ b/cmd/skopeo/signing.go @@ -10,7 +10,26 @@ import ( "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") if len(c.Args()) != 3 || outputFile == "" { 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 } -var standaloneSignCmd = cli.Command{ - 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`", - }, - }, +type standaloneVerifyOptions struct { } -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 { 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 } -var standaloneVerifyCmd = cli.Command{ - Name: "standalone-verify", - Usage: "Verify a signature using local files", - ArgsUsage: "MANIFEST DOCKER-REFERENCE KEY-FINGERPRINT SIGNATURE", - Action: standaloneVerify, +// 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. +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 { 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)) 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, -}