diff --git a/cmd/skopeo/copy.go b/cmd/skopeo/copy.go index f5b2e9c5..a1a71310 100644 --- a/cmd/skopeo/copy.go +++ b/cmd/skopeo/copy.go @@ -42,8 +42,9 @@ type copyOptions struct { } func copyCmd(global *globalOptions) cli.Command { - srcFlags, srcOpts := imageFlags(global, "src-", "screds") - destFlags, destOpts := imageDestFlags(global, "dest-", "dcreds") + sharedFlags, sharedOpts := sharedImageFlags() + srcFlags, srcOpts := imageFlags(global, sharedOpts, "src-", "screds") + destFlags, destOpts := imageDestFlags(global, sharedOpts, "dest-", "dcreds") opts := copyOptions{global: global, srcImage: srcOpts, destImage: destOpts, @@ -64,16 +65,12 @@ func copyCmd(global *globalOptions) cli.Command { ArgsUsage: "SOURCE-IMAGE DESTINATION-IMAGE", Action: opts.run, // FIXME: Do we need to namespace the GPG aspect? - Flags: append(append([]cli.Flag{ + Flags: append(append(append([]cli.Flag{ cli.StringSliceFlag{ Name: "additional-tag", Usage: "additional tags (supports docker-archive)", Value: &opts.additionalTags, // Surprisingly StringSliceFlag does not support Destination:, but modifies Value: in place. }, - 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", @@ -89,7 +86,7 @@ func copyCmd(global *globalOptions) cli.Command { Usage: "`MANIFEST TYPE` (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)", Value: newOptionalStringValue(&opts.format), }, - }, srcFlags...), destFlags...), + }, sharedFlags...), srcFlags...), destFlags...), } } diff --git a/cmd/skopeo/delete.go b/cmd/skopeo/delete.go index a7d4f0d1..9a3723e8 100644 --- a/cmd/skopeo/delete.go +++ b/cmd/skopeo/delete.go @@ -16,7 +16,8 @@ type deleteOptions struct { } func deleteCmd(global *globalOptions) cli.Command { - imageFlags, imageOpts := imageFlags(global, "", "") + sharedFlags, sharedOpts := sharedImageFlags() + imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "") opts := deleteOptions{ global: global, image: imageOpts, @@ -34,12 +35,7 @@ func deleteCmd(global *globalOptions) cli.Command { `, strings.Join(transports.ListNames(), ", ")), ArgsUsage: "IMAGE-NAME", Action: opts.run, - Flags: append([]cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", - }, - }, imageFlags...), + Flags: append(sharedFlags, imageFlags...), } } diff --git a/cmd/skopeo/inspect.go b/cmd/skopeo/inspect.go index 0bd7c928..0b01f077 100644 --- a/cmd/skopeo/inspect.go +++ b/cmd/skopeo/inspect.go @@ -36,7 +36,8 @@ type inspectOptions struct { } func inspectCmd(global *globalOptions) cli.Command { - imageFlags, imageOpts := imageFlags(global, "", "") + sharedFlags, sharedOpts := sharedImageFlags() + imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "") opts := inspectOptions{ global: global, image: imageOpts, @@ -53,17 +54,13 @@ func inspectCmd(global *globalOptions) cli.Command { See skopeo(1) section "IMAGE NAMES" for the expected format `, strings.Join(transports.ListNames(), ", ")), ArgsUsage: "IMAGE-NAME", - Flags: append([]cli.Flag{ - cli.StringFlag{ - Name: "authfile", - Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", - }, + Flags: append(append([]cli.Flag{ cli.BoolFlag{ Name: "raw", Usage: "output raw manifest", Destination: &opts.raw, }, - }, imageFlags...), + }, sharedFlags...), imageFlags...), Action: opts.run, } } diff --git a/cmd/skopeo/layers.go b/cmd/skopeo/layers.go index e4c53518..8f5d08da 100644 --- a/cmd/skopeo/layers.go +++ b/cmd/skopeo/layers.go @@ -21,7 +21,8 @@ type layersOptions struct { } func layersCmd(global *globalOptions) cli.Command { - imageFlags, imageOpts := imageFlags(global, "", "") + sharedFlags, sharedOpts := sharedImageFlags() + imageFlags, imageOpts := imageFlags(global, sharedOpts, "", "") opts := layersOptions{ global: global, image: imageOpts, @@ -32,7 +33,7 @@ func layersCmd(global *globalOptions) cli.Command { ArgsUsage: "IMAGE-NAME [LAYER...]", Hidden: true, Action: opts.run, - Flags: imageFlags, + Flags: append(sharedFlags, imageFlags...), } } diff --git a/cmd/skopeo/utils.go b/cmd/skopeo/utils.go index 9a7ca3b0..d4c63841 100644 --- a/cmd/skopeo/utils.go +++ b/cmd/skopeo/utils.go @@ -10,22 +10,42 @@ import ( "github.com/urfave/cli" ) +// sharedImageOptions collects CLI flags which are image-related, but do not change across images. +// This really should be a part of globalOptions, but that would break existing users of (skopeo copy --authfile=). +type sharedImageOptions struct { + authFilePath string // Path to a */containers/auth.json +} + +// imageFlags prepares a collection of CLI flags writing into sharedImageOptions, and the managed sharedImageOptions structure. +func sharedImageFlags() ([]cli.Flag, *sharedImageOptions) { + opts := sharedImageOptions{} + return []cli.Flag{ + cli.StringFlag{ + Name: "authfile", + Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", + Destination: &opts.authFilePath, + }, + }, &opts +} + // imageOptions collects CLI flags which are the same across subcommands, but may be different for each image // (e.g. may differ between the source and destination of a copy) type imageOptions struct { - global *globalOptions // May be shared across several imageOptions instances. - flagPrefix string // FIXME: Drop this eventually. - credsOption optionalString // username[:password] for accessing a registry - dockerCertPath string // A directory using Docker-like *.{crt,cert,key} files for connecting to a registry or a daemon - tlsVerify optionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:) - sharedBlobDir string // A directory to use for OCI blobs, shared across repositories - dockerDaemonHost string // docker-daemon: host to connect to + global *globalOptions // May be shared across several imageOptions instances. + shared *sharedImageOptions // May be shared across several imageOptions instances. + flagPrefix string // FIXME: Drop this eventually. + credsOption optionalString // username[:password] for accessing a registry + dockerCertPath string // A directory using Docker-like *.{crt,cert,key} files for connecting to a registry or a daemon + tlsVerify optionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:) + sharedBlobDir string // A directory to use for OCI blobs, shared across repositories + dockerDaemonHost string // docker-daemon: host to connect to } // imageFlags prepares a collection of CLI flags writing into imageOptions, and the managed imageOptions structure. -func imageFlags(global *globalOptions, flagPrefix, credsOptionAlias string) ([]cli.Flag, *imageOptions) { +func imageFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) ([]cli.Flag, *imageOptions) { opts := imageOptions{ global: global, + shared: shared, flagPrefix: flagPrefix, } @@ -74,7 +94,7 @@ func contextFromImageOptions(c *cli.Context, opts *imageOptions) (*types.SystemC OSChoice: opts.global.overrideOS, DockerCertPath: opts.dockerCertPath, OCISharedBlobDirPath: opts.sharedBlobDir, - AuthFilePath: c.String("authfile"), + AuthFilePath: opts.shared.authFilePath, DockerDaemonHost: opts.dockerDaemonHost, DockerDaemonCertPath: opts.dockerCertPath, } @@ -106,8 +126,8 @@ type imageDestOptions struct { } // imageDestFlags prepares a collection of CLI flags writing into imageDestOptions, and the managed imageDestOptions structure. -func imageDestFlags(global *globalOptions, flagPrefix, credsOptionAlias string) ([]cli.Flag, *imageDestOptions) { - genericFlags, genericOptions := imageFlags(global, flagPrefix, credsOptionAlias) +func imageDestFlags(global *globalOptions, shared *sharedImageOptions, flagPrefix, credsOptionAlias string) ([]cli.Flag, *imageDestOptions) { + genericFlags, genericOptions := imageFlags(global, shared, flagPrefix, credsOptionAlias) opts := imageDestOptions{imageOptions: genericOptions} return append(genericFlags, []cli.Flag{ diff --git a/cmd/skopeo/utils_test.go b/cmd/skopeo/utils_test.go index 615a77f8..8dfdaa06 100644 --- a/cmd/skopeo/utils_test.go +++ b/cmd/skopeo/utils_test.go @@ -35,7 +35,8 @@ func fakeImageContext(t *testing.T, cmdName string, flagPrefix string, globalFla cmd := app.Command(cmdName) require.NotNil(t, cmd) - imageFlags, imageOpts := imageFlags(globalOpts, flagPrefix, "") + sharedFlags, sharedOpts := sharedImageFlags() + imageFlags, imageOpts := imageFlags(globalOpts, sharedOpts, flagPrefix, "") appliedFlags := map[string]struct{}{} // Ugly: cmd.Flags includes imageFlags as well. For now, we need cmd.Flags to apply here // to be able to test the non-Destination: flags, but we must not apply the same flag name twice. @@ -46,7 +47,7 @@ func fakeImageContext(t *testing.T, cmdName string, flagPrefix string, globalFla return strings.Split(f.GetName(), ",")[0] } flagSet := flag.NewFlagSet(cmd.Name, flag.ContinueOnError) - for _, f := range imageFlags { + for _, f := range append(sharedFlags, imageFlags...) { f.Apply(flagSet) appliedFlags[firstName(f)] = struct{}{} } @@ -145,7 +146,8 @@ func fakeImageDestContext(t *testing.T, cmdName string, flagPrefix string, globa cmd := app.Command(cmdName) require.NotNil(t, cmd) - imageFlags, imageOpts := imageDestFlags(globalOpts, flagPrefix, "") + sharedFlags, sharedOpts := sharedImageFlags() + imageFlags, imageOpts := imageDestFlags(globalOpts, sharedOpts, flagPrefix, "") appliedFlags := map[string]struct{}{} // Ugly: cmd.Flags includes imageFlags as well. For now, we need cmd.Flags to apply here // to be able to test the non-Destination: flags, but we must not apply the same flag name twice. @@ -156,7 +158,7 @@ func fakeImageDestContext(t *testing.T, cmdName string, flagPrefix string, globa return strings.Split(f.GetName(), ",")[0] } flagSet := flag.NewFlagSet(cmd.Name, flag.ContinueOnError) - for _, f := range imageFlags { + for _, f := range append(sharedFlags, imageFlags...) { f.Apply(flagSet) appliedFlags[firstName(f)] = struct{}{} }