Refactor content_trust cli/flags handling

Remove the global variable used. Allows easier unit testing.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
Vincent Demeester 2018-03-08 14:35:17 +01:00 committed by Daniel Nephin
parent 82f325ed81
commit 6e21829af4
26 changed files with 296 additions and 146 deletions

View File

@ -52,6 +52,7 @@ type Cli interface {
DefaultVersion() string DefaultVersion() string
ManifestStore() manifeststore.Store ManifestStore() manifeststore.Store
RegistryClient(bool) registryclient.RegistryClient RegistryClient(bool) registryclient.RegistryClient
IsTrusted() bool
} }
// DockerCli is an instance the docker command line client. // DockerCli is an instance the docker command line client.
@ -64,6 +65,7 @@ type DockerCli struct {
client client.APIClient client client.APIClient
serverInfo ServerInfo serverInfo ServerInfo
clientInfo ClientInfo clientInfo ClientInfo
isTrusted bool
} }
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified. // DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
@ -121,6 +123,11 @@ func (cli *DockerCli) ClientInfo() ClientInfo {
return cli.clientInfo return cli.clientInfo
} }
// IsTrusted returns if content trust is enabled for the cli
func (cli *DockerCli) IsTrusted() bool {
return cli.isTrusted
}
// ManifestStore returns a store for local manifests // ManifestStore returns a store for local manifests
func (cli *DockerCli) ManifestStore() manifeststore.Store { func (cli *DockerCli) ManifestStore() manifeststore.Store {
// TODO: support override default location from config file // TODO: support override default location from config file
@ -237,8 +244,8 @@ func (c ClientInfo) HasKubernetes() bool {
} }
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli { func NewDockerCli(in io.ReadCloser, out, err io.Writer, isTrusted bool) *DockerCli {
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err} return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err, isTrusted: isTrusted}
} }
// NewAPIClientFromFlags creates a new APIClient from command line flags // NewAPIClientFromFlags creates a new APIClient from command line flags

View File

@ -21,8 +21,9 @@ import (
) )
type createOptions struct { type createOptions struct {
name string name string
platform string platform string
untrusted bool
} }
// NewCreateCommand creates a new cobra.Command for `docker create` // NewCreateCommand creates a new cobra.Command for `docker create`
@ -53,7 +54,7 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
flags.Bool("help", false, "Print usage") flags.Bool("help", false, "Print usage")
command.AddPlatformFlag(flags, &opts.platform) command.AddPlatformFlag(flags, &opts.platform)
command.AddTrustVerificationFlags(flags) command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted())
copts = addFlags(flags) copts = addFlags(flags)
return cmd return cmd
} }
@ -64,7 +65,7 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *createOptions,
reportError(dockerCli.Err(), "create", err.Error(), true) reportError(dockerCli.Err(), "create", err.Error(), true)
return cli.StatusError{StatusCode: 125} return cli.StatusError{StatusCode: 125}
} }
response, err := createContainer(context.Background(), dockerCli, containerConfig, opts.name, opts.platform) response, err := createContainer(context.Background(), dockerCli, containerConfig, opts)
if err != nil { if err != nil {
return err return err
} }
@ -158,7 +159,8 @@ func newCIDFile(path string) (*cidFile, error) {
return &cidFile{path: path, file: f}, nil return &cidFile{path: path, file: f}, nil
} }
func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, name string, platform string) (*container.ContainerCreateCreatedBody, error) { // nolint: gocyclo
func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, opts *createOptions) (*container.ContainerCreateCreatedBody, error) {
config := containerConfig.Config config := containerConfig.Config
hostConfig := containerConfig.HostConfig hostConfig := containerConfig.HostConfig
networkingConfig := containerConfig.NetworkingConfig networkingConfig := containerConfig.NetworkingConfig
@ -182,7 +184,8 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig
if named, ok := ref.(reference.Named); ok { if named, ok := ref.(reference.Named); ok {
namedRef = reference.TagNameOnly(named) namedRef = reference.TagNameOnly(named)
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && command.IsTrusted() { isContentTrustEnabled := !opts.untrusted && dockerCli.IsTrusted()
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && isContentTrustEnabled {
var err error var err error
trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef, nil) trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef, nil)
if err != nil { if err != nil {
@ -193,7 +196,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig
} }
//create the container //create the container
response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name) response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name)
//if image not found try to pull it //if image not found try to pull it
if err != nil { if err != nil {
@ -201,7 +204,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig
fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef)) fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
// we don't want to write to stdout anything apart from container.ID // we don't want to write to stdout anything apart from container.ID
if err := pullImage(ctx, dockerCli, config.Image, platform, stderr); err != nil { if err := pullImage(ctx, dockerCli, config.Image, opts.platform, stderr); err != nil {
return nil, err return nil, err
} }
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil { if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
@ -211,7 +214,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig
} }
// Retry // Retry
var retryErr error var retryErr error
response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name) response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name)
if retryErr != nil { if retryErr != nil {
return nil, retryErr return nil, retryErr
} }

View File

@ -107,7 +107,10 @@ func TestCreateContainerPullsImageIfMissing(t *testing.T) {
}, },
HostConfig: &container.HostConfig{}, HostConfig: &container.HostConfig{},
} }
body, err := createContainer(context.Background(), cli, config, "name", runtime.GOOS) body, err := createContainer(context.Background(), cli, config, &createOptions{
name: "name",
platform: runtime.GOOS,
})
assert.NilError(t, err) assert.NilError(t, err)
expected := container.ContainerCreateCreatedBody{ID: containerID} expected := container.ContainerCreateCreatedBody{ID: containerID}
assert.Check(t, is.DeepEqual(expected, *body)) assert.Check(t, is.DeepEqual(expected, *body))

View File

@ -25,11 +25,10 @@ import (
) )
type runOptions struct { type runOptions struct {
createOptions
detach bool detach bool
sigProxy bool sigProxy bool
name string
detachKeys string detachKeys string
platform string
} }
// NewRunCommand create a new `docker run` command // NewRunCommand create a new `docker run` command
@ -64,7 +63,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
flags.Bool("help", false, "Print usage") flags.Bool("help", false, "Print usage")
command.AddPlatformFlag(flags, &opts.platform) command.AddPlatformFlag(flags, &opts.platform)
command.AddTrustVerificationFlags(flags) command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted())
copts = addFlags(flags) copts = addFlags(flags)
return cmd return cmd
} }
@ -162,7 +161,7 @@ func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptio
ctx, cancelFun := context.WithCancel(context.Background()) ctx, cancelFun := context.WithCancel(context.Background())
createResponse, err := createContainer(ctx, dockerCli, containerConfig, opts.name, opts.platform) createResponse, err := createContainer(ctx, dockerCli, containerConfig, &opts.createOptions)
if err != nil { if err != nil {
reportError(stderr, cmdPath, err.Error(), true) reportError(stderr, cmdPath, err.Error(), true)
return runStartContainerErr(err) return runStartContainerErr(err)

View File

@ -67,6 +67,7 @@ type buildOptions struct {
imageIDFile string imageIDFile string
stream bool stream bool
platform string platform string
untrusted bool
} }
// dockerfileFromStdin returns true when the user specified that the Dockerfile // dockerfileFromStdin returns true when the user specified that the Dockerfile
@ -137,7 +138,7 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVar(&options.target, "target", "", "Set the target build stage to build.") flags.StringVar(&options.target, "target", "", "Set the target build stage to build.")
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file") flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
command.AddTrustVerificationFlags(flags) command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.IsTrusted())
command.AddPlatformFlag(flags, &options.platform) command.AddPlatformFlag(flags, &options.platform)
flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer") flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer")
@ -285,7 +286,8 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
defer cancel() defer cancel()
var resolvedTags []*resolvedTag var resolvedTags []*resolvedTag
if command.IsTrusted() { isContentTrustEnabled := !options.untrusted && dockerCli.IsTrusted()
if isContentTrustEnabled {
translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) { translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
return TrustedReference(ctx, dockerCli, ref, nil) return TrustedReference(ctx, dockerCli, ref, nil)
} }
@ -293,10 +295,10 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
if buildCtx != nil { if buildCtx != nil {
// Wrap the tar archive to replace the Dockerfile entry with the rewritten // Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls. // Dockerfile which uses trusted pulls.
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags) buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags, isContentTrustEnabled)
} else if dockerfileCtx != nil { } else if dockerfileCtx != nil {
// if there was not archive context still do the possible replacements in Dockerfile // if there was not archive context still do the possible replacements in Dockerfile
newDockerfile, _, err := rewriteDockerfileFrom(ctx, dockerfileCtx, translator) newDockerfile, _, err := rewriteDockerfileFrom(ctx, dockerfileCtx, translator, isContentTrustEnabled)
if err != nil { if err != nil {
return err return err
} }
@ -460,7 +462,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
return err return err
} }
} }
if command.IsTrusted() { if !options.untrusted && dockerCli.IsTrusted() {
// Since the build was successful, now we must tag any of the resolved // Since the build was successful, now we must tag any of the resolved
// images from the above Dockerfile rewrite. // images from the above Dockerfile rewrite.
for _, resolved := range resolvedTags { for _, resolved := range resolvedTags {
@ -503,7 +505,7 @@ type resolvedTag struct {
// "FROM <image>" instructions to a digest reference. `translator` is a // "FROM <image>" instructions to a digest reference. `translator` is a
// function that takes a repository name and tag reference and returns a // function that takes a repository name and tag reference and returns a
// trusted digest reference. // trusted digest reference.
func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator translatorFunc) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) { func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator translatorFunc, istrusted bool) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) {
scanner := bufio.NewScanner(dockerfile) scanner := bufio.NewScanner(dockerfile)
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
@ -520,7 +522,7 @@ func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator
return nil, nil, err return nil, nil, err
} }
ref = reference.TagNameOnly(ref) ref = reference.TagNameOnly(ref)
if ref, ok := ref.(reference.NamedTagged); ok && command.IsTrusted() { if ref, ok := ref.(reference.NamedTagged); ok && istrusted {
trustedRef, err := translator(ctx, ref) trustedRef, err := translator(ctx, ref)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -547,7 +549,7 @@ func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator
// replaces the entry with the given Dockerfile name with the contents of the // replaces the entry with the given Dockerfile name with the contents of the
// new Dockerfile. Returns a new tar archive stream with the replaced // new Dockerfile. Returns a new tar archive stream with the replaced
// Dockerfile. // Dockerfile.
func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag) io.ReadCloser { func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag, istrusted bool) io.ReadCloser {
pipeReader, pipeWriter := io.Pipe() pipeReader, pipeWriter := io.Pipe()
go func() { go func() {
tarReader := tar.NewReader(inputTarStream) tarReader := tar.NewReader(inputTarStream)
@ -574,7 +576,7 @@ func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadClos
// generated from a directory on the local filesystem, the // generated from a directory on the local filesystem, the
// Dockerfile will only appear once in the archive. // Dockerfile will only appear once in the archive.
var newDockerfile []byte var newDockerfile []byte
newDockerfile, *resolvedTags, err = rewriteDockerfileFrom(ctx, content, translator) newDockerfile, *resolvedTags, err = rewriteDockerfileFrom(ctx, content, translator, istrusted)
if err != nil { if err != nil {
pipeWriter.CloseWithError(err) pipeWriter.CloseWithError(err)
return return

View File

@ -14,9 +14,10 @@ import (
) )
type pullOptions struct { type pullOptions struct {
remote string remote string
all bool all bool
platform string platform string
untrusted bool
} }
// NewPullCommand creates a new `docker pull` command // NewPullCommand creates a new `docker pull` command
@ -38,7 +39,7 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command {
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository") flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository")
command.AddPlatformFlag(flags, &opts.platform) command.AddPlatformFlag(flags, &opts.platform)
command.AddTrustVerificationFlags(flags) command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted())
return cmd return cmd
} }
@ -65,7 +66,7 @@ func runPull(cli command.Cli, opts pullOptions) error {
// Check if reference has a digest // Check if reference has a digest
_, isCanonical := distributionRef.(reference.Canonical) _, isCanonical := distributionRef.(reference.Canonical)
if command.IsTrusted() && !isCanonical { if !opts.untrusted && cli.IsTrusted() && !isCanonical {
err = trustedPull(ctx, cli, imgRefAndAuth, opts.platform) err = trustedPull(ctx, cli, imgRefAndAuth, opts.platform)
} else { } else {
err = imagePullPrivileged(ctx, cli, imgRefAndAuth, opts.all, opts.platform) err = imagePullPrivileged(ctx, cli, imgRefAndAuth, opts.all, opts.platform)

View File

@ -8,6 +8,7 @@ import (
"testing" "testing"
"github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/notary"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/gotestyourself/gotestyourself/assert" "github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "github.com/gotestyourself/gotestyourself/assert/cmp"
@ -77,3 +78,44 @@ func TestNewPullCommandSuccess(t *testing.T) {
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("pull-command-success.%s.golden", tc.name)) golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("pull-command-success.%s.golden", tc.name))
} }
} }
func TestNewPullCommandWithContentTrustErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
notaryFunc test.NotaryClientFuncType
}{
{
name: "offline-notary-server",
notaryFunc: notary.GetOfflineNotaryRepository,
expectedError: "client is offline",
args: []string{"image:tag"},
},
{
name: "empty-notary-server",
notaryFunc: notary.GetUninitializedNotaryRepository,
expectedError: "remote trust data does not exist",
args: []string{"image:tag"},
},
{
name: "empty-notary-server",
notaryFunc: notary.GetEmptyTargetsNotaryRepository,
expectedError: "No valid trust data for tag",
args: []string{"image:tag"},
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{
imagePullFunc: func(ref string, options types.ImagePullOptions) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), fmt.Errorf("shouldn't try to pull image")
},
}, test.IsTrusted)
cli.SetNotaryClient(tc.notaryFunc)
cmd := NewPullCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.ErrorContains(t, err, tc.expectedError)
}
}

View File

@ -11,26 +11,34 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
type pushOptions struct {
remote string
untrusted bool
}
// NewPushCommand creates a new `docker push` command // NewPushCommand creates a new `docker push` command
func NewPushCommand(dockerCli command.Cli) *cobra.Command { func NewPushCommand(dockerCli command.Cli) *cobra.Command {
var opts pushOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "push [OPTIONS] NAME[:TAG]", Use: "push [OPTIONS] NAME[:TAG]",
Short: "Push an image or a repository to a registry", Short: "Push an image or a repository to a registry",
Args: cli.ExactArgs(1), Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runPush(dockerCli, args[0]) opts.remote = args[0]
return runPush(dockerCli, opts)
}, },
} }
flags := cmd.Flags() flags := cmd.Flags()
command.AddTrustSigningFlags(flags) command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.IsTrusted())
return cmd return cmd
} }
func runPush(dockerCli command.Cli, remote string) error { func runPush(dockerCli command.Cli, opts pushOptions) error {
ref, err := reference.ParseNormalizedNamed(remote) ref, err := reference.ParseNormalizedNamed(opts.remote)
if err != nil { if err != nil {
return err return err
} }
@ -47,7 +55,7 @@ func runPush(dockerCli command.Cli, remote string) error {
authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index) authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push") requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push")
if command.IsTrusted() { if !opts.untrusted && dockerCli.IsTrusted() {
return TrustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege) return TrustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege)
} }

View File

@ -37,11 +37,6 @@ func TestNewPushCommandErrors(t *testing.T) {
return ioutil.NopCloser(strings.NewReader("")), errors.Errorf("Failed to push") return ioutil.NopCloser(strings.NewReader("")), errors.Errorf("Failed to push")
}, },
}, },
{
name: "trust-error",
args: []string{"--disable-content-trust=false", "image:repo"},
expectedError: "you are not authorized to perform this operation: server returned 401.",
},
} }
for _, tc := range testCases { for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc}) cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc})

View File

@ -24,11 +24,12 @@ type pluginOptions struct {
disable bool disable bool
args []string args []string
skipRemoteCheck bool skipRemoteCheck bool
untrusted bool
} }
func loadPullFlags(opts *pluginOptions, flags *pflag.FlagSet) { func loadPullFlags(dockerCli command.Cli, opts *pluginOptions, flags *pflag.FlagSet) {
flags.BoolVar(&opts.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin") flags.BoolVar(&opts.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin")
command.AddTrustVerificationFlags(flags) command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted())
} }
func newInstallCommand(dockerCli command.Cli) *cobra.Command { func newInstallCommand(dockerCli command.Cli) *cobra.Command {
@ -47,7 +48,7 @@ func newInstallCommand(dockerCli command.Cli) *cobra.Command {
} }
flags := cmd.Flags() flags := cmd.Flags()
loadPullFlags(&options, flags) loadPullFlags(dockerCli, &options, flags)
flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install") flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install")
flags.StringVar(&options.localName, "alias", "", "Local name for plugin") flags.StringVar(&options.localName, "alias", "", "Local name for plugin")
return cmd return cmd
@ -90,7 +91,7 @@ func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOpti
remote := ref.String() remote := ref.String()
_, isCanonical := ref.(reference.Canonical) _, isCanonical := ref.(reference.Canonical)
if command.IsTrusted() && !isCanonical { if !opts.untrusted && dockerCli.IsTrusted() && !isCanonical {
ref = reference.TagNameOnly(ref) ref = reference.TagNameOnly(ref)
nt, ok := ref.(reference.NamedTagged) nt, ok := ref.(reference.NamedTagged)
if !ok { if !ok {

View File

@ -13,30 +13,37 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
type pushOptions struct {
name string
untrusted bool
}
func newPushCommand(dockerCli command.Cli) *cobra.Command { func newPushCommand(dockerCli command.Cli) *cobra.Command {
var opts pushOptions
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "push [OPTIONS] PLUGIN[:TAG]", Use: "push [OPTIONS] PLUGIN[:TAG]",
Short: "Push a plugin to a registry", Short: "Push a plugin to a registry",
Args: cli.ExactArgs(1), Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runPush(dockerCli, args[0]) opts.name = args[0]
return runPush(dockerCli, opts)
}, },
} }
flags := cmd.Flags() flags := cmd.Flags()
command.AddTrustSigningFlags(flags) command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.IsTrusted())
return cmd return cmd
} }
func runPush(dockerCli command.Cli, name string) error { func runPush(dockerCli command.Cli, opts pushOptions) error {
named, err := reference.ParseNormalizedNamed(name) named, err := reference.ParseNormalizedNamed(opts.name)
if err != nil { if err != nil {
return err return err
} }
if _, ok := named.(reference.Canonical); ok { if _, ok := named.(reference.Canonical); ok {
return errors.Errorf("invalid name: %s", name) return errors.Errorf("invalid name: %s", opts.name)
} }
named = reference.TagNameOnly(named) named = reference.TagNameOnly(named)
@ -60,7 +67,7 @@ func runPush(dockerCli command.Cli, name string) error {
} }
defer responseBody.Close() defer responseBody.Close()
if command.IsTrusted() { if !opts.untrusted && dockerCli.IsTrusted() {
repoInfo.Class = "plugin" repoInfo.Class = "plugin"
return image.PushTrustedReference(dockerCli, repoInfo, named, authConfig, responseBody) return image.PushTrustedReference(dockerCli, repoInfo, named, authConfig, responseBody)
} }

View File

@ -30,7 +30,7 @@ func newUpgradeCommand(dockerCli command.Cli) *cobra.Command {
} }
flags := cmd.Flags() flags := cmd.Flags()
loadPullFlags(&options, flags) loadPullFlags(dockerCli, &options, flags)
flags.BoolVar(&options.skipRemoteCheck, "skip-remote-check", false, "Do not check if specified remote plugin matches existing plugin image") flags.BoolVar(&options.skipRemoteCheck, "skip-remote-check", false, "Do not check if specified remote plugin matches existing plugin image")
return cmd return cmd
} }

View File

@ -16,7 +16,7 @@ import (
) )
func resolveServiceImageDigestContentTrust(dockerCli command.Cli, service *swarm.ServiceSpec) error { func resolveServiceImageDigestContentTrust(dockerCli command.Cli, service *swarm.ServiceSpec) error {
if !command.IsTrusted() { if !dockerCli.IsTrusted() {
// When not using content trust, digest resolution happens later when // When not using content trust, digest resolution happens later when
// contacting the registry to retrieve image information. // contacting the registry to retrieve image information.
return nil return nil

View File

@ -1,47 +1,15 @@
package command package command
import ( import (
"os"
"strconv"
"github.com/spf13/pflag" "github.com/spf13/pflag"
) )
var (
// TODO: make this not global
untrusted bool
)
func init() {
untrusted = !getDefaultTrustState()
}
// AddTrustVerificationFlags adds content trust flags to the provided flagset // AddTrustVerificationFlags adds content trust flags to the provided flagset
func AddTrustVerificationFlags(fs *pflag.FlagSet) { func AddTrustVerificationFlags(fs *pflag.FlagSet, v *bool, trusted bool) {
trusted := getDefaultTrustState() fs.BoolVar(v, "disable-content-trust", !trusted, "Skip image verification")
fs.BoolVar(&untrusted, "disable-content-trust", !trusted, "Skip image verification")
} }
// AddTrustSigningFlags adds "signing" flags to the provided flagset // AddTrustSigningFlags adds "signing" flags to the provided flagset
func AddTrustSigningFlags(fs *pflag.FlagSet) { func AddTrustSigningFlags(fs *pflag.FlagSet, v *bool, trusted bool) {
trusted := getDefaultTrustState() fs.BoolVar(v, "disable-content-trust", !trusted, "Skip image signing")
fs.BoolVar(&untrusted, "disable-content-trust", !trusted, "Skip image signing")
}
// getDefaultTrustState returns true if content trust is enabled through the $DOCKER_CONTENT_TRUST environment variable.
func getDefaultTrustState() bool {
var trusted bool
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
if t, err := strconv.ParseBool(e); t || err != nil {
// treat any other value as true
trusted = true
}
}
return trusted
}
// IsTrusted returns true if content trust is enabled, either through the $DOCKER_CONTENT_TRUST environment variable,
// or through `--disabled-content-trust=false` on a command.
func IsTrusted() bool {
return !untrusted
} }

View File

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/notary"
"github.com/gotestyourself/gotestyourself/assert" "github.com/gotestyourself/gotestyourself/assert"
"github.com/gotestyourself/gotestyourself/golden" "github.com/gotestyourself/gotestyourself/golden"
) )
@ -46,14 +47,14 @@ func TestTrustInspectCommandErrors(t *testing.T) {
func TestTrustInspectCommandOfflineErrors(t *testing.T) { func TestTrustInspectCommandOfflineErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository) cli.SetNotaryClient(notary.GetOfflineNotaryRepository)
cmd := newInspectCommand(cli) cmd := newInspectCommand(cli)
cmd.SetArgs([]string{"nonexistent-reg-name.io/image"}) cmd.SetArgs([]string{"nonexistent-reg-name.io/image"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image") assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image")
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository) cli.SetNotaryClient(notary.GetOfflineNotaryRepository)
cmd = newInspectCommand(cli) cmd = newInspectCommand(cli)
cmd.SetArgs([]string{"nonexistent-reg-name.io/image:tag"}) cmd.SetArgs([]string{"nonexistent-reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -62,7 +63,7 @@ func TestTrustInspectCommandOfflineErrors(t *testing.T) {
func TestTrustInspectCommandUninitializedErrors(t *testing.T) { func TestTrustInspectCommandUninitializedErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository) cli.SetNotaryClient(notary.GetUninitializedNotaryRepository)
cmd := newInspectCommand(cli) cmd := newInspectCommand(cli)
cmd.SetArgs([]string{"reg/unsigned-img"}) cmd.SetArgs([]string{"reg/unsigned-img"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -70,7 +71,7 @@ func TestTrustInspectCommandUninitializedErrors(t *testing.T) {
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-uninitialized.golden") golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-uninitialized.golden")
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository) cli.SetNotaryClient(notary.GetUninitializedNotaryRepository)
cmd = newInspectCommand(cli) cmd = newInspectCommand(cli)
cmd.SetArgs([]string{"reg/unsigned-img:tag"}) cmd.SetArgs([]string{"reg/unsigned-img:tag"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -80,7 +81,7 @@ func TestTrustInspectCommandUninitializedErrors(t *testing.T) {
func TestTrustInspectCommandEmptyNotaryRepo(t *testing.T) { func TestTrustInspectCommandEmptyNotaryRepo(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cli.SetNotaryClient(notary.GetEmptyTargetsNotaryRepository)
cmd := newInspectCommand(cli) cmd := newInspectCommand(cli)
cmd.SetArgs([]string{"reg/img:unsigned-tag"}) cmd.SetArgs([]string{"reg/img:unsigned-tag"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -90,7 +91,7 @@ func TestTrustInspectCommandEmptyNotaryRepo(t *testing.T) {
func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) { func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository) cli.SetNotaryClient(notary.GetLoadedWithNoSignersNotaryRepository)
cmd := newInspectCommand(cli) cmd := newInspectCommand(cli)
cmd.SetArgs([]string{"signed-repo"}) cmd.SetArgs([]string{"signed-repo"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -99,7 +100,7 @@ func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) {
func TestTrustInspectCommandOneTagWithoutSigners(t *testing.T) { func TestTrustInspectCommandOneTagWithoutSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository) cli.SetNotaryClient(notary.GetLoadedWithNoSignersNotaryRepository)
cmd := newInspectCommand(cli) cmd := newInspectCommand(cli)
cmd.SetArgs([]string{"signed-repo:green"}) cmd.SetArgs([]string{"signed-repo:green"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -108,7 +109,7 @@ func TestTrustInspectCommandOneTagWithoutSigners(t *testing.T) {
func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) { func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedNotaryRepository) cli.SetNotaryClient(notary.GetLoadedNotaryRepository)
cmd := newInspectCommand(cli) cmd := newInspectCommand(cli)
cmd.SetArgs([]string{"signed-repo"}) cmd.SetArgs([]string{"signed-repo"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -117,7 +118,7 @@ func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) {
func TestTrustInspectCommandMultipleFullReposWithSigners(t *testing.T) { func TestTrustInspectCommandMultipleFullReposWithSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedNotaryRepository) cli.SetNotaryClient(notary.GetLoadedNotaryRepository)
cmd := newInspectCommand(cli) cmd := newInspectCommand(cli)
cmd.SetArgs([]string{"signed-repo", "signed-repo"}) cmd.SetArgs([]string{"signed-repo", "signed-repo"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -126,7 +127,7 @@ func TestTrustInspectCommandMultipleFullReposWithSigners(t *testing.T) {
func TestTrustInspectCommandUnsignedTagInSignedRepo(t *testing.T) { func TestTrustInspectCommandUnsignedTagInSignedRepo(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedNotaryRepository) cli.SetNotaryClient(notary.GetLoadedNotaryRepository)
cmd := newInspectCommand(cli) cmd := newInspectCommand(cli)
cmd.SetArgs([]string{"signed-repo:unsigned"}) cmd.SetArgs([]string{"signed-repo:unsigned"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())

View File

@ -6,6 +6,7 @@ import (
"testing" "testing"
"github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/notary"
"github.com/gotestyourself/gotestyourself/assert" "github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/theupdateframework/notary/client" "github.com/theupdateframework/notary/client"
@ -55,7 +56,7 @@ func TestTrustRevokeCommandErrors(t *testing.T) {
func TestTrustRevokeCommandOfflineErrors(t *testing.T) { func TestTrustRevokeCommandOfflineErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository) cli.SetNotaryClient(notary.GetOfflineNotaryRepository)
cmd := newRevokeCommand(cli) cmd := newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image"}) cmd.SetArgs([]string{"reg-name.io/image"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -63,13 +64,13 @@ func TestTrustRevokeCommandOfflineErrors(t *testing.T) {
assert.Check(t, is.Contains(cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.")) assert.Check(t, is.Contains(cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action."))
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository) cli.SetNotaryClient(notary.GetOfflineNotaryRepository)
cmd = newRevokeCommand(cli) cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image", "-y"}) cmd.SetArgs([]string{"reg-name.io/image", "-y"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository) cli.SetNotaryClient(notary.GetOfflineNotaryRepository)
cmd = newRevokeCommand(cli) cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetArgs([]string{"reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -78,7 +79,7 @@ func TestTrustRevokeCommandOfflineErrors(t *testing.T) {
func TestTrustRevokeCommandUninitializedErrors(t *testing.T) { func TestTrustRevokeCommandUninitializedErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository) cli.SetNotaryClient(notary.GetUninitializedNotaryRepository)
cmd := newRevokeCommand(cli) cmd := newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image"}) cmd.SetArgs([]string{"reg-name.io/image"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -86,14 +87,14 @@ func TestTrustRevokeCommandUninitializedErrors(t *testing.T) {
assert.Check(t, is.Contains(cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.")) assert.Check(t, is.Contains(cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action."))
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository) cli.SetNotaryClient(notary.GetUninitializedNotaryRepository)
cmd = newRevokeCommand(cli) cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image", "-y"}) cmd.SetArgs([]string{"reg-name.io/image", "-y"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image: does not have trust data for") assert.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image: does not have trust data for")
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository) cli.SetNotaryClient(notary.GetUninitializedNotaryRepository)
cmd = newRevokeCommand(cli) cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetArgs([]string{"reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -102,7 +103,7 @@ func TestTrustRevokeCommandUninitializedErrors(t *testing.T) {
func TestTrustRevokeCommandEmptyNotaryRepo(t *testing.T) { func TestTrustRevokeCommandEmptyNotaryRepo(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cli.SetNotaryClient(notary.GetEmptyTargetsNotaryRepository)
cmd := newRevokeCommand(cli) cmd := newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image"}) cmd.SetArgs([]string{"reg-name.io/image"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -110,14 +111,14 @@ func TestTrustRevokeCommandEmptyNotaryRepo(t *testing.T) {
assert.Check(t, is.Contains(cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.")) assert.Check(t, is.Contains(cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action."))
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cli.SetNotaryClient(notary.GetEmptyTargetsNotaryRepository)
cmd = newRevokeCommand(cli) cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image", "-y"}) cmd.SetArgs([]string{"reg-name.io/image", "-y"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image: no signed tags to remove") assert.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image: no signed tags to remove")
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cli.SetNotaryClient(notary.GetEmptyTargetsNotaryRepository)
cmd = newRevokeCommand(cli) cmd = newRevokeCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetArgs([]string{"reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -126,7 +127,7 @@ func TestTrustRevokeCommandEmptyNotaryRepo(t *testing.T) {
func TestNewRevokeTrustAllSigConfirmation(t *testing.T) { func TestNewRevokeTrustAllSigConfirmation(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cli.SetNotaryClient(notary.GetEmptyTargetsNotaryRepository)
cmd := newRevokeCommand(cli) cmd := newRevokeCommand(cli)
cmd.SetArgs([]string{"alpine"}) cmd.SetArgs([]string{"alpine"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/trust" "github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test"
notaryfake "github.com/docker/cli/internal/test/notary"
"github.com/gotestyourself/gotestyourself/assert" "github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/skip" "github.com/gotestyourself/gotestyourself/skip"
@ -76,7 +77,7 @@ func TestTrustSignCommandErrors(t *testing.T) {
func TestTrustSignCommandOfflineErrors(t *testing.T) { func TestTrustSignCommandOfflineErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository) cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository)
cmd := newSignCommand(cli) cmd := newSignCommand(cli)
cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetArgs([]string{"reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -282,7 +283,7 @@ func TestSignCommandChangeListIsCleanedOnError(t *testing.T) {
config.SetDir(tmpDir) config.SetDir(tmpDir)
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedNotaryRepository) cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository)
cmd := newSignCommand(cli) cmd := newSignCommand(cli)
cmd.SetArgs([]string{"ubuntu:latest"}) cmd.SetArgs([]string{"ubuntu:latest"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -299,7 +300,7 @@ func TestSignCommandChangeListIsCleanedOnError(t *testing.T) {
func TestSignCommandLocalFlag(t *testing.T) { func TestSignCommandLocalFlag(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository)
cmd := newSignCommand(cli) cmd := newSignCommand(cli)
cmd.SetArgs([]string{"--local", "reg-name.io/image:red"}) cmd.SetArgs([]string{"--local", "reg-name.io/image:red"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/cli/cli/config" "github.com/docker/cli/cli/config"
"github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test"
notaryfake "github.com/docker/cli/internal/test/notary"
"github.com/gotestyourself/gotestyourself/assert" "github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/theupdateframework/notary" "github.com/theupdateframework/notary"
@ -58,7 +59,7 @@ func TestTrustSignerAddErrors(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository) cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository)
cmd := newSignerAddCommand(cli) cmd := newSignerAddCommand(cli)
cmd.SetArgs(tc.args) cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -77,7 +78,7 @@ func TestSignerAddCommandNoTargetsKey(t *testing.T) {
defer os.Remove(tmpfile.Name()) defer os.Remove(tmpfile.Name())
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository)
cmd := newSignerAddCommand(cli) cmd := newSignerAddCommand(cli)
cmd.SetArgs([]string{"--key", tmpfile.Name(), "alice", "alpine", "linuxkit/alpine"}) cmd.SetArgs([]string{"--key", tmpfile.Name(), "alice", "alpine", "linuxkit/alpine"})
@ -92,7 +93,7 @@ func TestSignerAddCommandBadKeyPath(t *testing.T) {
config.SetDir(tmpDir) config.SetDir(tmpDir)
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository)
cmd := newSignerAddCommand(cli) cmd := newSignerAddCommand(cli)
cmd.SetArgs([]string{"--key", "/path/to/key.pem", "alice", "alpine"}) cmd.SetArgs([]string{"--key", "/path/to/key.pem", "alice", "alpine"})
@ -117,7 +118,7 @@ func TestSignerAddCommandInvalidRepoName(t *testing.T) {
assert.NilError(t, ioutil.WriteFile(pubKeyFilepath, pubKeyFixture, notary.PrivNoExecPerms)) assert.NilError(t, ioutil.WriteFile(pubKeyFilepath, pubKeyFixture, notary.PrivNoExecPerms))
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository) cli.SetNotaryClient(notaryfake.GetUninitializedNotaryRepository)
cmd := newSignerAddCommand(cli) cmd := newSignerAddCommand(cli)
imageName := "870d292919d01a0af7e7f056271dc78792c05f55f49b9b9012b6d89725bd9abd" imageName := "870d292919d01a0af7e7f056271dc78792c05f55f49b9b9012b6d89725bd9abd"
cmd.SetArgs([]string{"--key", pubKeyFilepath, "alice", imageName}) cmd.SetArgs([]string{"--key", pubKeyFilepath, "alice", imageName})

View File

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test"
notaryfake "github.com/docker/cli/internal/test/notary"
"github.com/gotestyourself/gotestyourself/assert" "github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/theupdateframework/notary/client" "github.com/theupdateframework/notary/client"
@ -57,7 +58,7 @@ func TestTrustSignerRemoveErrors(t *testing.T) {
} }
for _, tc := range testCasesWithOutput { for _, tc := range testCasesWithOutput {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository) cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository)
cmd := newSignerRemoveCommand(cli) cmd := newSignerRemoveCommand(cli)
cmd.SetArgs(tc.args) cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -69,7 +70,7 @@ func TestTrustSignerRemoveErrors(t *testing.T) {
func TestRemoveSingleSigner(t *testing.T) { func TestRemoveSingleSigner(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedNotaryRepository) cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository)
err := removeSingleSigner(cli, "signed-repo", "test", true) err := removeSingleSigner(cli, "signed-repo", "test", true)
assert.Error(t, err, "No signer test for repository signed-repo") assert.Error(t, err, "No signer test for repository signed-repo")
err = removeSingleSigner(cli, "signed-repo", "releases", true) err = removeSingleSigner(cli, "signed-repo", "releases", true)
@ -78,7 +79,7 @@ func TestRemoveSingleSigner(t *testing.T) {
func TestRemoveMultipleSigners(t *testing.T) { func TestRemoveMultipleSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedNotaryRepository) cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository)
err := removeSigner(cli, signerRemoveOptions{signer: "test", repos: []string{"signed-repo", "signed-repo"}, forceYes: true}) err := removeSigner(cli, signerRemoveOptions{signer: "test", repos: []string{"signed-repo", "signed-repo"}, forceYes: true})
assert.Error(t, err, "Error removing signer from: signed-repo, signed-repo") assert.Error(t, err, "Error removing signer from: signed-repo, signed-repo")
assert.Check(t, is.Contains(cli.ErrBuffer().String(), assert.Check(t, is.Contains(cli.ErrBuffer().String(),
@ -87,7 +88,7 @@ func TestRemoveMultipleSigners(t *testing.T) {
} }
func TestRemoveLastSignerWarning(t *testing.T) { func TestRemoveLastSignerWarning(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedNotaryRepository) cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository)
err := removeSigner(cli, signerRemoveOptions{signer: "alice", repos: []string{"signed-repo"}, forceYes: false}) err := removeSigner(cli, signerRemoveOptions{signer: "alice", repos: []string{"signed-repo"}, forceYes: false})
assert.NilError(t, err) assert.NilError(t, err)

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/cli/cli/trust" "github.com/docker/cli/cli/trust"
"github.com/docker/cli/internal/test" "github.com/docker/cli/internal/test"
notaryfake "github.com/docker/cli/internal/test/notary"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/gotestyourself/gotestyourself/assert" "github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp" is "github.com/gotestyourself/gotestyourself/assert/cmp"
@ -57,14 +58,14 @@ func TestTrustViewCommandErrors(t *testing.T) {
func TestTrustViewCommandOfflineErrors(t *testing.T) { func TestTrustViewCommandOfflineErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository) cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository)
cmd := newViewCommand(cli) cmd := newViewCommand(cli)
cmd.SetArgs([]string{"nonexistent-reg-name.io/image"}) cmd.SetArgs([]string{"nonexistent-reg-name.io/image"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image") assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image")
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getOfflineNotaryRepository) cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository)
cmd = newViewCommand(cli) cmd = newViewCommand(cli)
cmd.SetArgs([]string{"nonexistent-reg-name.io/image:tag"}) cmd.SetArgs([]string{"nonexistent-reg-name.io/image:tag"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -73,14 +74,14 @@ func TestTrustViewCommandOfflineErrors(t *testing.T) {
func TestTrustViewCommandUninitializedErrors(t *testing.T) { func TestTrustViewCommandUninitializedErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository) cli.SetNotaryClient(notaryfake.GetUninitializedNotaryRepository)
cmd := newViewCommand(cli) cmd := newViewCommand(cli)
cmd.SetArgs([]string{"reg/unsigned-img"}) cmd.SetArgs([]string{"reg/unsigned-img"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img") assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img")
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository) cli.SetNotaryClient(notaryfake.GetUninitializedNotaryRepository)
cmd = newViewCommand(cli) cmd = newViewCommand(cli)
cmd.SetArgs([]string{"reg/unsigned-img:tag"}) cmd.SetArgs([]string{"reg/unsigned-img:tag"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -89,7 +90,7 @@ func TestTrustViewCommandUninitializedErrors(t *testing.T) {
func TestTrustViewCommandEmptyNotaryRepoErrors(t *testing.T) { func TestTrustViewCommandEmptyNotaryRepoErrors(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository)
cmd := newViewCommand(cli) cmd := newViewCommand(cli)
cmd.SetArgs([]string{"reg/img:unsigned-tag"}) cmd.SetArgs([]string{"reg/img:unsigned-tag"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -98,7 +99,7 @@ func TestTrustViewCommandEmptyNotaryRepoErrors(t *testing.T) {
assert.Check(t, is.Contains(cli.OutBuffer().String(), "Administrative keys for reg/img:")) assert.Check(t, is.Contains(cli.OutBuffer().String(), "Administrative keys for reg/img:"))
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getEmptyTargetsNotaryRepository) cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository)
cmd = newViewCommand(cli) cmd = newViewCommand(cli)
cmd.SetArgs([]string{"reg/img"}) cmd.SetArgs([]string{"reg/img"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
@ -109,7 +110,7 @@ func TestTrustViewCommandEmptyNotaryRepoErrors(t *testing.T) {
func TestTrustViewCommandFullRepoWithoutSigners(t *testing.T) { func TestTrustViewCommandFullRepoWithoutSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository) cli.SetNotaryClient(notaryfake.GetLoadedWithNoSignersNotaryRepository)
cmd := newViewCommand(cli) cmd := newViewCommand(cli)
cmd.SetArgs([]string{"signed-repo"}) cmd.SetArgs([]string{"signed-repo"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -119,7 +120,7 @@ func TestTrustViewCommandFullRepoWithoutSigners(t *testing.T) {
func TestTrustViewCommandOneTagWithoutSigners(t *testing.T) { func TestTrustViewCommandOneTagWithoutSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository) cli.SetNotaryClient(notaryfake.GetLoadedWithNoSignersNotaryRepository)
cmd := newViewCommand(cli) cmd := newViewCommand(cli)
cmd.SetArgs([]string{"signed-repo:green"}) cmd.SetArgs([]string{"signed-repo:green"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -129,7 +130,7 @@ func TestTrustViewCommandOneTagWithoutSigners(t *testing.T) {
func TestTrustViewCommandFullRepoWithSigners(t *testing.T) { func TestTrustViewCommandFullRepoWithSigners(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedNotaryRepository) cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository)
cmd := newViewCommand(cli) cmd := newViewCommand(cli)
cmd.SetArgs([]string{"signed-repo"}) cmd.SetArgs([]string{"signed-repo"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())
@ -139,7 +140,7 @@ func TestTrustViewCommandFullRepoWithSigners(t *testing.T) {
func TestTrustViewCommandUnsignedTagInSignedRepo(t *testing.T) { func TestTrustViewCommandUnsignedTagInSignedRepo(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{}) cli := test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getLoadedNotaryRepository) cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository)
cmd := newViewCommand(cli) cmd := newViewCommand(cli)
cmd.SetArgs([]string{"signed-repo:unsigned"}) cmd.SetArgs([]string{"signed-repo:unsigned"})
assert.NilError(t, cmd.Execute()) assert.NilError(t, cmd.Execute())

View File

@ -4,6 +4,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"strconv"
"strings" "strings"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
@ -155,7 +156,7 @@ func main() {
stdin, stdout, stderr := term.StdStreams() stdin, stdout, stderr := term.StdStreams()
logrus.SetOutput(stderr) logrus.SetOutput(stderr)
dockerCli := command.NewDockerCli(stdin, stdout, stderr) dockerCli := command.NewDockerCli(stdin, stdout, stderr, isContentTrustEnabled())
cmd := newDockerCommand(dockerCli) cmd := newDockerCommand(dockerCli)
if err := cmd.Execute(); err != nil { if err := cmd.Execute(); err != nil {
@ -175,6 +176,16 @@ func main() {
} }
} }
func isContentTrustEnabled() bool {
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
if t, err := strconv.ParseBool(e); t || err != nil {
// treat any other value as true
return true
}
}
return false
}
func showVersion() { func showVersion() {
fmt.Printf("Docker version %s, build %s\n", cli.Version, cli.GitCommit) fmt.Printf("Docker version %s, build %s\n", cli.Version, cli.GitCommit)
} }

View File

@ -26,7 +26,7 @@ func TestClientDebugEnabled(t *testing.T) {
func TestExitStatusForInvalidSubcommandWithHelpFlag(t *testing.T) { func TestExitStatusForInvalidSubcommandWithHelpFlag(t *testing.T) {
discard := ioutil.Discard discard := ioutil.Discard
cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard)) cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard, false))
cmd.SetArgs([]string{"help", "invalid"}) cmd.SetArgs([]string{"help", "invalid"})
err := cmd.Execute() err := cmd.Execute()
assert.Error(t, err, "unknown help topic: invalid") assert.Error(t, err, "unknown help topic: invalid")

View File

@ -19,7 +19,7 @@ const descriptionSourcePath = "docs/reference/commandline/"
func generateCliYaml(opts *options) error { func generateCliYaml(opts *options) error {
stdin, stdout, stderr := term.StdStreams() stdin, stdout, stderr := term.StdStreams()
dockerCli := command.NewDockerCli(stdin, stdout, stderr) dockerCli := command.NewDockerCli(stdin, stdout, stderr, false)
cmd := &cobra.Command{Use: "docker"} cmd := &cobra.Command{Use: "docker"}
commands.AddCommands(cmd, dockerCli) commands.AddCommands(cmd, dockerCli)
source := filepath.Join(opts.source, descriptionSourcePath) source := filepath.Join(opts.source, descriptionSourcePath)

View File

@ -16,7 +16,8 @@ import (
notaryclient "github.com/theupdateframework/notary/client" notaryclient "github.com/theupdateframework/notary/client"
) )
type notaryClientFuncType func(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) // NotaryClientFuncType defines a function that returns a fake notary client
type NotaryClientFuncType func(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
type clientInfoFuncType func() command.ClientInfo type clientInfoFuncType func() command.ClientInfo
// FakeCli emulates the default DockerCli // FakeCli emulates the default DockerCli
@ -30,16 +31,17 @@ type FakeCli struct {
in *command.InStream in *command.InStream
server command.ServerInfo server command.ServerInfo
clientInfoFunc clientInfoFuncType clientInfoFunc clientInfoFuncType
notaryClientFunc notaryClientFuncType notaryClientFunc NotaryClientFuncType
manifestStore manifeststore.Store manifestStore manifeststore.Store
registryClient registryclient.RegistryClient registryClient registryclient.RegistryClient
isTrusted bool
} }
// NewFakeCli returns a fake for the command.Cli interface // NewFakeCli returns a fake for the command.Cli interface
func NewFakeCli(client client.APIClient) *FakeCli { func NewFakeCli(client client.APIClient, opts ...func(*FakeCli)) *FakeCli {
outBuffer := new(bytes.Buffer) outBuffer := new(bytes.Buffer)
errBuffer := new(bytes.Buffer) errBuffer := new(bytes.Buffer)
return &FakeCli{ c := &FakeCli{
client: client, client: client,
out: command.NewOutStream(outBuffer), out: command.NewOutStream(outBuffer),
outBuffer: outBuffer, outBuffer: outBuffer,
@ -49,6 +51,10 @@ func NewFakeCli(client client.APIClient) *FakeCli {
// Set cli.ConfigFile().Filename to a tempfile to support Save. // Set cli.ConfigFile().Filename to a tempfile to support Save.
configfile: configfile.New(""), configfile: configfile.New(""),
} }
for _, opt := range opts {
opt(c)
}
return c
} }
// SetIn sets the input of the cli to the specified ReadCloser // SetIn sets the input of the cli to the specified ReadCloser
@ -120,7 +126,7 @@ func (c *FakeCli) ErrBuffer() *bytes.Buffer {
} }
// SetNotaryClient sets the internal getter for retrieving a NotaryClient // SetNotaryClient sets the internal getter for retrieving a NotaryClient
func (c *FakeCli) SetNotaryClient(notaryClientFunc notaryClientFuncType) { func (c *FakeCli) SetNotaryClient(notaryClientFunc NotaryClientFuncType) {
c.notaryClientFunc = notaryClientFunc c.notaryClientFunc = notaryClientFunc
} }
@ -151,3 +157,13 @@ func (c *FakeCli) SetManifestStore(store manifeststore.Store) {
func (c *FakeCli) SetRegistryClient(client registryclient.RegistryClient) { func (c *FakeCli) SetRegistryClient(client registryclient.RegistryClient) {
c.registryClient = client c.registryClient = client
} }
// IsTrusted on the fake cli
func (c *FakeCli) IsTrusted() bool {
return c.isTrusted
}
// IsTrusted sets "enables" content trust on the fake cli
func IsTrusted(c *FakeCli) {
c.isTrusted = true
}

View File

@ -1,4 +1,4 @@
package trust package notary
import ( import (
"github.com/docker/cli/cli/trust" "github.com/docker/cli/cli/trust"
@ -12,107 +12,142 @@ import (
"github.com/theupdateframework/notary/tuf/signed" "github.com/theupdateframework/notary/tuf/signed"
) )
// Sample mock CLI interfaces // GetOfflineNotaryRepository returns a OfflineNotaryRepository
func GetOfflineNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
func getOfflineNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return OfflineNotaryRepository{}, nil return OfflineNotaryRepository{}, nil
} }
// OfflineNotaryRepository is a mock Notary repository that is offline // OfflineNotaryRepository is a mock Notary repository that is offline
type OfflineNotaryRepository struct{} type OfflineNotaryRepository struct{}
// Initialize creates a new repository by using rootKey as the root Key for the
// TUF repository.
func (o OfflineNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { func (o OfflineNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error {
return storage.ErrOffline{} return storage.ErrOffline{}
} }
// InitializeWithCertificate initializes the repository with root keys and their corresponding certificates
func (o OfflineNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { func (o OfflineNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error {
return storage.ErrOffline{} return storage.ErrOffline{}
} }
// Publish pushes the local changes in signed material to the remote notary-server
// Conceptually it performs an operation similar to a `git rebase`
func (o OfflineNotaryRepository) Publish() error { func (o OfflineNotaryRepository) Publish() error {
return storage.ErrOffline{} return storage.ErrOffline{}
} }
// AddTarget creates new changelist entries to add a target to the given roles
// in the repository when the changelist gets applied at publish time.
func (o OfflineNotaryRepository) AddTarget(target *client.Target, roles ...data.RoleName) error { func (o OfflineNotaryRepository) AddTarget(target *client.Target, roles ...data.RoleName) error {
return nil return nil
} }
// RemoveTarget creates new changelist entries to remove a target from the given
// roles in the repository when the changelist gets applied at publish time.
func (o OfflineNotaryRepository) RemoveTarget(targetName string, roles ...data.RoleName) error { func (o OfflineNotaryRepository) RemoveTarget(targetName string, roles ...data.RoleName) error {
return nil return nil
} }
// ListTargets lists all targets for the current repository. The list of
// roles should be passed in order from highest to lowest priority.
func (o OfflineNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { func (o OfflineNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
return nil, storage.ErrOffline{} return nil, storage.ErrOffline{}
} }
// GetTargetByName returns a target by the given name.
func (o OfflineNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { func (o OfflineNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
return nil, storage.ErrOffline{} return nil, storage.ErrOffline{}
} }
// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all
// roles, and returns a list of TargetSignedStructs for each time it finds the specified target.
func (o OfflineNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { func (o OfflineNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
return nil, storage.ErrOffline{} return nil, storage.ErrOffline{}
} }
// GetChangelist returns the list of the repository's unpublished changes
func (o OfflineNotaryRepository) GetChangelist() (changelist.Changelist, error) { func (o OfflineNotaryRepository) GetChangelist() (changelist.Changelist, error) {
return changelist.NewMemChangelist(), nil return changelist.NewMemChangelist(), nil
} }
// ListRoles returns a list of RoleWithSignatures objects for this repo
func (o OfflineNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { func (o OfflineNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
return nil, storage.ErrOffline{} return nil, storage.ErrOffline{}
} }
// GetDelegationRoles returns the keys and roles of the repository's delegations
func (o OfflineNotaryRepository) GetDelegationRoles() ([]data.Role, error) { func (o OfflineNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return nil, storage.ErrOffline{} return nil, storage.ErrOffline{}
} }
// AddDelegation creates changelist entries to add provided delegation public keys and paths.
func (o OfflineNotaryRepository) AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error { func (o OfflineNotaryRepository) AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error {
return nil return nil
} }
// AddDelegationRoleAndKeys creates a changelist entry to add provided delegation public keys.
func (o OfflineNotaryRepository) AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error { func (o OfflineNotaryRepository) AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error {
return nil return nil
} }
// AddDelegationPaths creates a changelist entry to add provided paths to an existing delegation.
func (o OfflineNotaryRepository) AddDelegationPaths(name data.RoleName, paths []string) error { func (o OfflineNotaryRepository) AddDelegationPaths(name data.RoleName, paths []string) error {
return nil return nil
} }
// RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and paths.
func (o OfflineNotaryRepository) RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error { func (o OfflineNotaryRepository) RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error {
return nil return nil
} }
// RemoveDelegationRole creates a changelist to remove all paths and keys from a role, and delete the role in its entirety.
func (o OfflineNotaryRepository) RemoveDelegationRole(name data.RoleName) error { func (o OfflineNotaryRepository) RemoveDelegationRole(name data.RoleName) error {
return nil return nil
} }
// RemoveDelegationPaths creates a changelist entry to remove provided paths from an existing delegation.
func (o OfflineNotaryRepository) RemoveDelegationPaths(name data.RoleName, paths []string) error { func (o OfflineNotaryRepository) RemoveDelegationPaths(name data.RoleName, paths []string) error {
return nil return nil
} }
// RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation.
func (o OfflineNotaryRepository) RemoveDelegationKeys(name data.RoleName, keyIDs []string) error { func (o OfflineNotaryRepository) RemoveDelegationKeys(name data.RoleName, keyIDs []string) error {
return nil return nil
} }
// ClearDelegationPaths creates a changelist entry to remove all paths from an existing delegation.
func (o OfflineNotaryRepository) ClearDelegationPaths(name data.RoleName) error { func (o OfflineNotaryRepository) ClearDelegationPaths(name data.RoleName) error {
return nil return nil
} }
// Witness creates change objects to witness (i.e. re-sign) the given
// roles on the next publish. One change is created per role
func (o OfflineNotaryRepository) Witness(roles ...data.RoleName) ([]data.RoleName, error) { func (o OfflineNotaryRepository) Witness(roles ...data.RoleName) ([]data.RoleName, error) {
return nil, nil return nil, nil
} }
// RotateKey rotates a private key and returns the public component from the remote server
func (o OfflineNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { func (o OfflineNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error {
return storage.ErrOffline{} return storage.ErrOffline{}
} }
// GetCryptoService is the getter for the repository's CryptoService
func (o OfflineNotaryRepository) GetCryptoService() signed.CryptoService { func (o OfflineNotaryRepository) GetCryptoService() signed.CryptoService {
return nil return nil
} }
// SetLegacyVersions allows the number of legacy versions of the root
// to be inspected for old signing keys to be configured.
func (o OfflineNotaryRepository) SetLegacyVersions(version int) {} func (o OfflineNotaryRepository) SetLegacyVersions(version int) {}
// GetGUN is a getter for the GUN object from a Repository
func (o OfflineNotaryRepository) GetGUN() data.GUN { func (o OfflineNotaryRepository) GetGUN() data.GUN {
return data.GUN("gun") return data.GUN("gun")
} }
func getUninitializedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { // GetUninitializedNotaryRepository returns an UninitializedNotaryRepository
func GetUninitializedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return UninitializedNotaryRepository{}, nil return UninitializedNotaryRepository{}, nil
} }
@ -123,42 +158,57 @@ type UninitializedNotaryRepository struct {
OfflineNotaryRepository OfflineNotaryRepository
} }
// Initialize creates a new repository by using rootKey as the root Key for the
// TUF repository.
func (u UninitializedNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { func (u UninitializedNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error {
return client.ErrRepositoryNotExist{} return client.ErrRepositoryNotExist{}
} }
// InitializeWithCertificate initializes the repository with root keys and their corresponding certificates
func (u UninitializedNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { func (u UninitializedNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error {
return client.ErrRepositoryNotExist{} return client.ErrRepositoryNotExist{}
} }
// Publish pushes the local changes in signed material to the remote notary-server
// Conceptually it performs an operation similar to a `git rebase`
func (u UninitializedNotaryRepository) Publish() error { func (u UninitializedNotaryRepository) Publish() error {
return client.ErrRepositoryNotExist{} return client.ErrRepositoryNotExist{}
} }
// ListTargets lists all targets for the current repository. The list of
// roles should be passed in order from highest to lowest priority.
func (u UninitializedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { func (u UninitializedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
return nil, client.ErrRepositoryNotExist{} return nil, client.ErrRepositoryNotExist{}
} }
// GetTargetByName returns a target by the given name.
func (u UninitializedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { func (u UninitializedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
return nil, client.ErrRepositoryNotExist{} return nil, client.ErrRepositoryNotExist{}
} }
// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all
// roles, and returns a list of TargetSignedStructs for each time it finds the specified target.
func (u UninitializedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { func (u UninitializedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
return nil, client.ErrRepositoryNotExist{} return nil, client.ErrRepositoryNotExist{}
} }
// ListRoles returns a list of RoleWithSignatures objects for this repo
func (u UninitializedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { func (u UninitializedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
return nil, client.ErrRepositoryNotExist{} return nil, client.ErrRepositoryNotExist{}
} }
// GetDelegationRoles returns the keys and roles of the repository's delegations
func (u UninitializedNotaryRepository) GetDelegationRoles() ([]data.Role, error) { func (u UninitializedNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return nil, client.ErrRepositoryNotExist{} return nil, client.ErrRepositoryNotExist{}
} }
// RotateKey rotates a private key and returns the public component from the remote server
func (u UninitializedNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { func (u UninitializedNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error {
return client.ErrRepositoryNotExist{} return client.ErrRepositoryNotExist{}
} }
func getEmptyTargetsNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { // GetEmptyTargetsNotaryRepository returns an EmptyTargetsNotaryRepository
func GetEmptyTargetsNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return EmptyTargetsNotaryRepository{}, nil return EmptyTargetsNotaryRepository{}, nil
} }
@ -168,29 +218,41 @@ type EmptyTargetsNotaryRepository struct {
OfflineNotaryRepository OfflineNotaryRepository
} }
// Initialize creates a new repository by using rootKey as the root Key for the
// TUF repository.
func (e EmptyTargetsNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { func (e EmptyTargetsNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error {
return nil return nil
} }
// InitializeWithCertificate initializes the repository with root keys and their corresponding certificates
func (e EmptyTargetsNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { func (e EmptyTargetsNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error {
return nil return nil
} }
// Publish pushes the local changes in signed material to the remote notary-server
// Conceptually it performs an operation similar to a `git rebase`
func (e EmptyTargetsNotaryRepository) Publish() error { func (e EmptyTargetsNotaryRepository) Publish() error {
return nil return nil
} }
// ListTargets lists all targets for the current repository. The list of
// roles should be passed in order from highest to lowest priority.
func (e EmptyTargetsNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { func (e EmptyTargetsNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
return []*client.TargetWithRole{}, nil return []*client.TargetWithRole{}, nil
} }
// GetTargetByName returns a target by the given name.
func (e EmptyTargetsNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { func (e EmptyTargetsNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
return nil, client.ErrNoSuchTarget(name) return nil, client.ErrNoSuchTarget(name)
} }
// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all
// roles, and returns a list of TargetSignedStructs for each time it finds the specified target.
func (e EmptyTargetsNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { func (e EmptyTargetsNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
return nil, client.ErrNoSuchTarget(name) return nil, client.ErrNoSuchTarget(name)
} }
// ListRoles returns a list of RoleWithSignatures objects for this repo
func (e EmptyTargetsNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { func (e EmptyTargetsNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
rootRole := data.Role{ rootRole := data.Role{
RootRole: data.RootRole{ RootRole: data.RootRole{
@ -212,15 +274,18 @@ func (e EmptyTargetsNotaryRepository) ListRoles() ([]client.RoleWithSignatures,
{Role: targetsRole}}, nil {Role: targetsRole}}, nil
} }
// GetDelegationRoles returns the keys and roles of the repository's delegations
func (e EmptyTargetsNotaryRepository) GetDelegationRoles() ([]data.Role, error) { func (e EmptyTargetsNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return []data.Role{}, nil return []data.Role{}, nil
} }
// RotateKey rotates a private key and returns the public component from the remote server
func (e EmptyTargetsNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { func (e EmptyTargetsNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error {
return nil return nil
} }
func getLoadedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { // GetLoadedNotaryRepository returns a LoadedNotaryRepository
func GetLoadedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return LoadedNotaryRepository{}, nil return LoadedNotaryRepository{}, nil
} }
@ -316,6 +381,7 @@ var loadedTargets = []client.TargetSignedStruct{
{Target: loadedGreenTarget, Role: loadedReleasesRole}, {Target: loadedGreenTarget, Role: loadedReleasesRole},
} }
// ListRoles returns a list of RoleWithSignatures objects for this repo
func (l LoadedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { func (l LoadedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
rootRole := data.Role{ rootRole := data.Role{
RootRole: data.RootRole{ RootRole: data.RootRole{
@ -368,6 +434,8 @@ func (l LoadedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error)
}, nil }, nil
} }
// ListTargets lists all targets for the current repository. The list of
// roles should be passed in order from highest to lowest priority.
func (l LoadedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { func (l LoadedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
filteredTargets := []*client.TargetWithRole{} filteredTargets := []*client.TargetWithRole{}
for _, tgt := range loadedTargets { for _, tgt := range loadedTargets {
@ -378,6 +446,7 @@ func (l LoadedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.T
return filteredTargets, nil return filteredTargets, nil
} }
// GetTargetByName returns a target by the given name.
func (l LoadedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { func (l LoadedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
for _, tgt := range loadedTargets { for _, tgt := range loadedTargets {
if name == tgt.Target.Name { if name == tgt.Target.Name {
@ -389,6 +458,8 @@ func (l LoadedNotaryRepository) GetTargetByName(name string, roles ...data.RoleN
return nil, client.ErrNoSuchTarget(name) return nil, client.ErrNoSuchTarget(name)
} }
// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all
// roles, and returns a list of TargetSignedStructs for each time it finds the specified target.
func (l LoadedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { func (l LoadedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
if name == "" { if name == "" {
return loadedTargets, nil return loadedTargets, nil
@ -405,14 +476,17 @@ func (l LoadedNotaryRepository) GetAllTargetMetadataByName(name string) ([]clien
return filteredTargets, nil return filteredTargets, nil
} }
// GetGUN is a getter for the GUN object from a Repository
func (l LoadedNotaryRepository) GetGUN() data.GUN { func (l LoadedNotaryRepository) GetGUN() data.GUN {
return data.GUN("signed-repo") return data.GUN("signed-repo")
} }
// GetDelegationRoles returns the keys and roles of the repository's delegations
func (l LoadedNotaryRepository) GetDelegationRoles() ([]data.Role, error) { func (l LoadedNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return loadedDelegationRoles, nil return loadedDelegationRoles, nil
} }
// GetCryptoService is the getter for the repository's CryptoService
func (l LoadedNotaryRepository) GetCryptoService() signed.CryptoService { func (l LoadedNotaryRepository) GetCryptoService() signed.CryptoService {
if l.statefulCryptoService == nil { if l.statefulCryptoService == nil {
// give it an in-memory cryptoservice with a root key and targets key // give it an in-memory cryptoservice with a root key and targets key
@ -423,7 +497,8 @@ func (l LoadedNotaryRepository) GetCryptoService() signed.CryptoService {
return l.statefulCryptoService return l.statefulCryptoService
} }
func getLoadedWithNoSignersNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { // GetLoadedWithNoSignersNotaryRepository returns a LoadedWithNoSignersNotaryRepository
func GetLoadedWithNoSignersNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return LoadedWithNoSignersNotaryRepository{}, nil return LoadedWithNoSignersNotaryRepository{}, nil
} }
@ -433,6 +508,8 @@ type LoadedWithNoSignersNotaryRepository struct {
LoadedNotaryRepository LoadedNotaryRepository
} }
// ListTargets lists all targets for the current repository. The list of
// roles should be passed in order from highest to lowest priority.
func (l LoadedWithNoSignersNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { func (l LoadedWithNoSignersNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
filteredTargets := []*client.TargetWithRole{} filteredTargets := []*client.TargetWithRole{}
for _, tgt := range loadedTargets { for _, tgt := range loadedTargets {
@ -443,6 +520,7 @@ func (l LoadedWithNoSignersNotaryRepository) ListTargets(roles ...data.RoleName)
return filteredTargets, nil return filteredTargets, nil
} }
// GetTargetByName returns a target by the given name.
func (l LoadedWithNoSignersNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { func (l LoadedWithNoSignersNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
if name == "" || name == loadedGreenTarget.Name { if name == "" || name == loadedGreenTarget.Name {
return &client.TargetWithRole{Target: loadedGreenTarget, Role: data.CanonicalTargetsRole}, nil return &client.TargetWithRole{Target: loadedGreenTarget, Role: data.CanonicalTargetsRole}, nil
@ -450,6 +528,8 @@ func (l LoadedWithNoSignersNotaryRepository) GetTargetByName(name string, roles
return nil, client.ErrNoSuchTarget(name) return nil, client.ErrNoSuchTarget(name)
} }
// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all
// roles, and returns a list of TargetSignedStructs for each time it finds the specified target.
func (l LoadedWithNoSignersNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { func (l LoadedWithNoSignersNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
if name == "" || name == loadedGreenTarget.Name { if name == "" || name == loadedGreenTarget.Name {
return []client.TargetSignedStruct{{Target: loadedGreenTarget, Role: loadedTargetsRole}}, nil return []client.TargetSignedStruct{{Target: loadedGreenTarget, Role: loadedTargetsRole}}, nil
@ -457,6 +537,7 @@ func (l LoadedWithNoSignersNotaryRepository) GetAllTargetMetadataByName(name str
return nil, client.ErrNoSuchTarget(name) return nil, client.ErrNoSuchTarget(name)
} }
// GetDelegationRoles returns the keys and roles of the repository's delegations
func (l LoadedWithNoSignersNotaryRepository) GetDelegationRoles() ([]data.Role, error) { func (l LoadedWithNoSignersNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return []data.Role{}, nil return []data.Role{}, nil
} }

View File

@ -25,7 +25,7 @@ func generateManPages(opts *options) error {
} }
stdin, stdout, stderr := term.StdStreams() stdin, stdout, stderr := term.StdStreams()
dockerCli := command.NewDockerCli(stdin, stdout, stderr) dockerCli := command.NewDockerCli(stdin, stdout, stderr, false)
cmd := &cobra.Command{Use: "docker"} cmd := &cobra.Command{Use: "docker"}
commands.AddCommands(cmd, dockerCli) commands.AddCommands(cmd, dockerCli)
source := filepath.Join(opts.source, descriptionSourcePath) source := filepath.Join(opts.source, descriptionSourcePath)