Compare commits

..

11 Commits

Author SHA1 Message Date
ae21824df8 Merge pull request #223 from seemethere/bump_17090rc1
[17.09] bump version to 17.09.0-ce-rc1
2017-09-06 15:22:01 -07:00
ec4c78a39b Merge pull request #214 from vieux/17.09-changelog
[17.09] add 17.09 changelog
2017-09-06 15:21:47 -07:00
aa56783035 Merge pull request #224 from seemethere/fix_integration_tests_1709
[17.09] Changes error check form NotNil to IsNil
2017-09-06 15:18:32 -07:00
b33e8d49d4 Add skip for known failure
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-09-06 13:41:55 -07:00
5d1587e61e Changes error check form NotNil to IsNil
rmi -f always returns a 0 exit code so these tests needed to be changed
accordingly.

Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-09-06 11:30:29 -07:00
8574cd6b8a bump version to 17.09.0-ce-rc1
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-09-05 17:06:13 -07:00
54ddbdea25 Merge pull request #222 from vieux/fix_inspect
[17.09] Fix integration suite and propagate failures
2017-09-05 16:49:27 -07:00
efd69761fc Fix integration suite and propagate failures
Failures from the integration suite were not propagating to the outter shell
for some reason. Handle the failure with an if exit 1.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
(cherry picked from commit 96707bc600747257e82917ca079fa5006d636b2c)
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
2017-09-05 14:58:05 -07:00
c973c430a9 Merge pull request #220 from vieux/fix_nano_tests
[17.09] backport: force inspect test format
2017-09-05 14:43:58 -07:00
e3e65f6470 force inspect test format
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
(cherry picked from commit 8e6567cb837e1c885de5146517557c7a5d8a5f17)
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
2017-09-05 13:37:34 -07:00
f7ec4a69b3 add 17.09 changelog
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
2017-09-01 10:32:17 -07:00
1366 changed files with 45008 additions and 92550 deletions

1
.gitignore vendored
View File

@ -1 +0,0 @@
.helpers/

View File

@ -5,42 +5,45 @@ information on the list of deprecated flags and APIs please have a look at
https://docs.docker.com/engine/deprecated/ where target removal dates can also
be found.
## 17.10.0-ce (2017-10-DD)
IMPORTANT: Starting with this release, `docker service create`, `docker service update`,
`docker service scale` and `docker service rollback` use non-detached mode as default,
use `--detach` to keep the old behaviour.
## 17.09.0-ce (2017-09-DD)
### Builder
* Reset uid/gid to 0 in uploaded build context to share build cache with other clients [docker/cli#513](https://github.com/docker/cli/pull/513)
+ Add support for `ADD` urls without any sub path [moby/moby#34217](https://github.com/moby/moby/pull/34217)
+ Add `--chown` flag to `ADD/COPY` commands in Dockerfile [moby/moby#34263](https://github.com/moby/moby/pull/34263)
* Fix cloning unneeded files while building from git repositories [moby/moby#33704](https://github.com/moby/moby/pull/33704)
### Client
* Move output of `docker stack rm` to stdout [docker/cli#491](https://github.com/docker/cli/pull/491)
* Use natural sort secrets and configs in cli [docker/cli#307](https://github.com/docker/cli/pull/307)
* Use non-detached mode as default for `docker service` commands [docker/cli#525](https://github.com/docker/cli/pull/525)
* Set APIVersion on the client, even when Ping fails [docker/cli#546](https://github.com/docker/cli/pull/546)
- Fix loader error with different build syntax in `docker stack deploy` [docker/cli#544](https://github.com/docker/cli/pull/544)
* Change the default output format for `docker container stats` to show `CONTAINER ID` and `NAME` [docker/cli#565](https://github.com/docker/cli/pull/565)
+ Add `--no-trunc` flag to `docker container stats` [docker/cli#565](https://github.com/docker/cli/pull/565)
+ Add experimental `docker trust`: `view`, `revoke`, `sign` subcommands [docker/cli#472](https://github.com/docker/cli/pull/472)
* Allow extension fields in the v3.4 version of the compose format [docker/cli#452](https://github.com/docker/cli/pull/452)
* Make compose file allow to specify names for non-external volume [docker/cli#306](https://github.com/docker/cli/pull/306)
* Support `--compose-file -` as stdin [docker/cli#347](https://github.com/docker/cli/pull/347)
* Support `start_period` for healthcheck in Docker Compose [docker/cli#475](https://github.com/docker/cli/pull/475)
+ Add support for `stop-signal` in docker stack commands [docker/cli#388](https://github.com/docker/cli/pull/388)
+ Add support for update order in compose deployments [docker/cli#360](https://github.com/docker/cli/pull/360)
+ Add ulimits to unsupported compose fields [docker/cli#482](https://github.com/docker/cli/pull/482)
+ Add `--format` to `docker-search` [docker/cli#440](https://github.com/docker/cli/pull/440)
* Show images digests when `{{.Digest}}` is in format [docker/cli#439](https://github.com/docker/cli/pull/439)
* Print timestamp when `--human=true` [docker/cli#438](https://github.com/docker/cli/pull/438)
### Networking
* Enabling ILB/ELB on windows using per-node, per-network LB endpoint [moby/moby#34674](https://github.com/moby/moby/pull/34674)
+ Add the control plane MTU option in the daemon config [moby/moby#34103](https://github.com/moby/moby/pull/34103)
+ Add service virtual IP to sandbox's loopback address [docker/libnetwork#1877](https://github.com/docker/libnetwork/pull/1877)
### Runtime
* LCOW: Add UVM debugability by grabbing logs before tear-down [moby/moby#34846](https://github.com/moby/moby/pull/34846)
* LCOW: Prepare work for bind mounts [moby/moby#34258](https://github.com/moby/moby/pull/34258)
* LCOW: Support for docker cp, ADD/COPY on build [moby/moby#34252](https://github.com/moby/moby/pull/34252)
* LCOW: VHDX boot to readonly [moby/moby#34754](https://github.com/moby/moby/pull/34754)
* Volume: evaluate symlinks before relabeling mount source [moby/moby#34792](https://github.com/moby/moby/pull/34792)
- Fixing docker cp to allow new target file name in a host symlinked directory [moby/moby#31993](https://github.com/moby/moby/pull/31993)
* Graphdriver: promote overlay2 over aufs [moby/moby#34430](https://github.com/moby/moby/pull/34430)
* LCOW: Additional flags for VHD boot [moby/moby#34451](https://github.com/moby/moby/pull/34451)
* LCOW: Don't block export [moby/moby#34448](https://github.com/moby/moby/pull/34448)
* LCOW: Dynamic sandbox management [moby/moby#34170](https://github.com/moby/moby/pull/34170)
* LCOW: Force Hyper-V Isolation [moby/moby#34468](https://github.com/moby/moby/pull/34468)
* LCOW: Move toolsScratchPath to /tmp [moby/moby#34396](https://github.com/moby/moby/pull/34396)
* LCOW: Remove hard-coding [moby/moby#34398](https://github.com/moby/moby/pull/34398)
* LCOW: WORKDIR correct handling [moby/moby#34405](https://github.com/moby/moby/pull/34405)
* Windows: named pipe mounts [moby/moby#33852](https://github.com/moby/moby/pull/33852)
### Swarm Mode
* Produce an error if `docker swarm init --force-new-cluster` is executed on worker nodes [moby/moby#34881](https://github.com/moby/moby/pull/34881)
+ Add support for `.Node.Hostname` templating in swarm services [moby/moby#34686](https://github.com/moby/moby/pull/34686)
* Include whether the managers in the swarm are autolocked as part of `docker info` [docker/cli#471](https://github.com/docker/cli/pull/471)
+ Add 'docker service rollback' subcommand [docker/cli#205](https://github.com/docker/cli/pull/205)

View File

@ -1,56 +1,27 @@
CLI_DIR:=$(CURDIR)/components/cli
ENGINE_DIR:=$(CURDIR)/components/engine
PACKAGING_DIR:=$(CURDIR)/components/packaging
MOBY_COMPONENTS_SHA=f79265f1412af0a68aadd11e1d2f374446f3681b
MOBY_COMPONENTS_URL=https://raw.githubusercontent.com/shykes/moby-extras/$(MOBY_COMPONENTS_SHA)/cmd/moby-components
MOBY_COMPONENTS=.helpers/moby-components-$(MOBY_COMPONENTS_SHA)
VERSION=$(shell cat VERSION)
.PHONY: help
help: ## show make targets
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
.PHONY: test-integration-cli
test-integration-cli: $(CLI_DIR)/build/docker ## test integration of cli and engine
$(MAKE) -C $(ENGINE_DIR) DOCKER_CLI_PATH=$< test-integration-cli
$(CLI_DIR)/build/docker:
$(MAKE) -C $(CLI_DIR) -f docker.Makefile build
.PHONY: deb
deb: ## build deb packages
$(MAKE) VERSION=$(VERSION) CLI_DIR=$(CLI_DIR) ENGINE_DIR=$(ENGINE_DIR) -C $(PACKAGING_DIR) deb
.PHONY: rpm
rpm: ## build rpm packages
$(MAKE) VERSION=$(VERSION) CLI_DIR=$(CLI_DIR) ENGINE_DIR=$(ENGINE_DIR) -C $(PACKAGING_DIR) rpm
.PHONY: static
static: ## build static packages
$(MAKE) VERSION=$(VERSION) CLI_DIR=$(CLI_DIR) ENGINE_DIR=$(ENGINE_DIR) -C $(PACKAGING_DIR) static
.PHONY: clean
clean: ## clean the build artifacts
-$(MAKE) -C $(CLI_DIR) clean
-$(MAKE) -C $(ENGINE_DIR) clean
-$(MAKE) -C $(PACKAGING_DIR) clean
$(MOBY_COMPONENTS):
mkdir -p .helpers
curl -fsSL $(MOBY_COMPONENTS_URL) > $(MOBY_COMPONENTS)
chmod +x $(MOBY_COMPONENTS)
.PHONY: update-components
update-components: update-components-cli update-components-engine update-components-packaging ## udpate components using moby extra tool
.PHONY: update-components-cli
update-components-cli: $(MOBY_COMPONENTS)
$(MOBY_COMPONENTS) update cli
.PHONY: update-components-engine
update-components-engine: $(MOBY_COMPONENTS)
$(MOBY_COMPONENTS) update engine
.PHONY: update-components-packaging
update-components-packaging: $(MOBY_COMPONENTS)
$(MOBY_COMPONENTS) update packaging

View File

@ -1 +1 @@
17.10.0-ce-rc2
17.09.0-ce-rc1

View File

@ -27,7 +27,6 @@
people = [
"aaronlehmann",
"albers",
"aluzzardi",
"anusha",
"cpuguy83",
@ -85,11 +84,6 @@
Email = "aaron.lehmann@docker.com"
GitHub = "aaronlehmann"
[people.albers]
Name = "Harald Albers"
Email = "github@albersweb.de"
GitHub = "albers"
[people.aluzzardi]
Name = "Andrea Luzzardi"
Email = "al@docker.com"

View File

@ -34,14 +34,6 @@ binary: ## build executable for Linux
cross: ## build executable for macOS and Windows
./scripts/build/cross
.PHONY: binary-windows
binary-windows: ## build executable for Windows
./scripts/build/windows
.PHONY: binary-osx
binary-osx: ## build executable for macOS
./scripts/build/osx
.PHONY: dynbinary
dynbinary: ## build dynamically linked binary
./scripts/build/dynbinary

View File

@ -1,4 +1,4 @@
[![build status](https://circleci.com/gh/docker/cli.svg?style=shield)](https://circleci.com/gh/docker/cli/tree/master) [![Build Status](https://jenkins.dockerproject.org/job/docker/job/cli/job/master/badge/icon)](https://jenkins.dockerproject.org/job/docker/job/cli/job/master/)
[![build status](https://circleci.com/gh/docker/cli.svg?style=shield)](https://circleci.com/gh/docker/cli/tree/master)
docker/cli
==========

View File

@ -1 +1 @@
17.10.0-ce-rc2
17.09.0-ce-rc1

View File

@ -12,14 +12,11 @@ import (
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
cliflags "github.com/docker/cli/cli/flags"
"github.com/docker/cli/cli/trust"
dopts "github.com/docker/cli/opts"
"github.com/docker/docker/api"
"github.com/docker/docker/client"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
"github.com/docker/notary"
notaryclient "github.com/docker/notary/client"
"github.com/docker/notary/passphrase"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -42,7 +39,6 @@ type Cli interface {
SetIn(in *InStream)
ConfigFile() *configfile.ConfigFile
ServerInfo() ServerInfo
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
}
// DockerCli is an instance the docker command line client.
@ -115,58 +111,44 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
var err error
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
if tlsconfig.IsErrEncryptedKey(err) {
var (
passwd string
giveup bool
)
passRetriever := passphrase.PromptRetrieverWithInOut(cli.In(), cli.Out(), nil)
newClient := func(password string) (client.APIClient, error) {
opts.Common.TLSOptions.Passphrase = password
return NewAPIClientFromFlags(opts.Common, cli.configFile)
for attempts := 0; tlsconfig.IsErrEncryptedKey(err); attempts++ {
// some code and comments borrowed from notary/trustmanager/keystore.go
passwd, giveup, err = passRetriever("private", "encrypted TLS private", false, attempts)
// Check if the passphrase retriever got an error or if it is telling us to give up
if giveup || err != nil {
return errors.Wrap(err, "private key is encrypted, but could not get passphrase")
}
opts.Common.TLSOptions.Passphrase = passwd
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
}
cli.client, err = getClientWithPassword(passRetriever, newClient)
}
if err != nil {
return err
}
cli.initializeFromClient()
return nil
}
func (cli *DockerCli) initializeFromClient() {
cli.defaultVersion = cli.client.ClientVersion()
ping, err := cli.client.Ping(context.Background())
if err != nil {
if ping, err := cli.client.Ping(context.Background()); err == nil {
cli.server = ServerInfo{
HasExperimental: ping.Experimental,
OSType: ping.OSType,
}
cli.client.NegotiateAPIVersionPing(ping)
} else {
// Default to true if we fail to connect to daemon
cli.server = ServerInfo{HasExperimental: true}
if ping.APIVersion != "" {
cli.client.NegotiateAPIVersionPing(ping)
}
return
}
cli.server = ServerInfo{
HasExperimental: ping.Experimental,
OSType: ping.OSType,
}
cli.client.NegotiateAPIVersionPing(ping)
}
func getClientWithPassword(passRetriever notary.PassRetriever, newClient func(password string) (client.APIClient, error)) (client.APIClient, error) {
for attempts := 0; ; attempts++ {
passwd, giveup, err := passRetriever("private", "encrypted TLS private", false, attempts)
if giveup || err != nil {
return nil, errors.Wrap(err, "private key is encrypted, but could not get passphrase")
}
apiclient, err := newClient(passwd)
if !tlsconfig.IsErrEncryptedKey(err) {
return apiclient, err
}
}
}
// NotaryClient provides a Notary Repository to interact with signed metadata for an image
func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) {
return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...)
return nil
}
// ServerInfo stores details about the supported features and platform of the

View File

@ -4,18 +4,12 @@ import (
"os"
"testing"
"crypto/x509"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/flags"
"github.com/docker/cli/internal/test/testutil"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestNewAPIClientFromFlags(t *testing.T) {
@ -49,7 +43,7 @@ func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
assert.Equal(t, customVersion, apiclient.ClientVersion())
}
// TODO: use gotestyourself/env.Patch
// TODO: move to gotestyourself
func patchEnvVariable(t *testing.T, key, value string) func() {
oldValue, ok := os.LookupEnv(key)
require.NoError(t, os.Setenv(key, value))
@ -61,138 +55,3 @@ func patchEnvVariable(t *testing.T, key, value string) func() {
require.NoError(t, os.Setenv(key, oldValue))
}
}
type fakeClient struct {
client.Client
pingFunc func() (types.Ping, error)
version string
negotiated bool
}
func (c *fakeClient) Ping(_ context.Context) (types.Ping, error) {
return c.pingFunc()
}
func (c *fakeClient) ClientVersion() string {
return c.version
}
func (c *fakeClient) NegotiateAPIVersionPing(types.Ping) {
c.negotiated = true
}
func TestInitializeFromClient(t *testing.T) {
defaultVersion := "v1.55"
var testcases = []struct {
doc string
pingFunc func() (types.Ping, error)
expectedServer ServerInfo
negotiated bool
}{
{
doc: "successful ping",
pingFunc: func() (types.Ping, error) {
return types.Ping{Experimental: true, OSType: "linux", APIVersion: "v1.30"}, nil
},
expectedServer: ServerInfo{HasExperimental: true, OSType: "linux"},
negotiated: true,
},
{
doc: "failed ping, no API version",
pingFunc: func() (types.Ping, error) {
return types.Ping{}, errors.New("failed")
},
expectedServer: ServerInfo{HasExperimental: true},
},
{
doc: "failed ping, with API version",
pingFunc: func() (types.Ping, error) {
return types.Ping{APIVersion: "v1.33"}, errors.New("failed")
},
expectedServer: ServerInfo{HasExperimental: true},
negotiated: true,
},
}
for _, testcase := range testcases {
t.Run(testcase.doc, func(t *testing.T) {
apiclient := &fakeClient{
pingFunc: testcase.pingFunc,
version: defaultVersion,
}
cli := &DockerCli{client: apiclient}
cli.initializeFromClient()
assert.Equal(t, defaultVersion, cli.defaultVersion)
assert.Equal(t, testcase.expectedServer, cli.server)
assert.Equal(t, testcase.negotiated, apiclient.negotiated)
})
}
}
func TestGetClientWithPassword(t *testing.T) {
expected := "password"
var testcases = []struct {
doc string
password string
retrieverErr error
retrieverGiveup bool
newClientErr error
expectedErr string
}{
{
doc: "successful connect",
password: expected,
},
{
doc: "password retriever exhausted",
retrieverGiveup: true,
retrieverErr: errors.New("failed"),
expectedErr: "private key is encrypted, but could not get passphrase",
},
{
doc: "password retriever error",
retrieverErr: errors.New("failed"),
expectedErr: "failed",
},
{
doc: "newClient error",
newClientErr: errors.New("failed to connect"),
expectedErr: "failed to connect",
},
}
for _, testcase := range testcases {
t.Run(testcase.doc, func(t *testing.T) {
passRetriever := func(_, _ string, _ bool, attempts int) (passphrase string, giveup bool, err error) {
// Always return an invalid pass first to test iteration
switch attempts {
case 0:
return "something else", false, nil
default:
return testcase.password, testcase.retrieverGiveup, testcase.retrieverErr
}
}
newClient := func(currentPassword string) (client.APIClient, error) {
if testcase.newClientErr != nil {
return nil, testcase.newClientErr
}
if currentPassword == expected {
return &client.Client{}, nil
}
return &client.Client{}, x509.IncorrectPasswordError
}
_, err := getClientWithPassword(passRetriever, newClient)
if testcase.expectedErr != "" {
testutil.ErrorContains(t, err, testcase.expectedErr)
return
}
assert.NoError(t, err)
})
}
}

View File

@ -17,7 +17,6 @@ import (
"github.com/docker/cli/cli/command/stack"
"github.com/docker/cli/cli/command/swarm"
"github.com/docker/cli/cli/command/system"
"github.com/docker/cli/cli/command/trust"
"github.com/docker/cli/cli/command/volume"
"github.com/spf13/cobra"
)
@ -70,9 +69,6 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) {
// swarm
swarm.NewSwarmCommand(dockerCli),
// trust
trust.NewTrustCommand(dockerCli),
// volume
volume.NewVolumeCommand(dockerCli),

View File

@ -1,27 +1,15 @@
package config
import (
"sort"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/spf13/cobra"
"golang.org/x/net/context"
"vbom.ml/util/sortorder"
)
type byConfigName []swarm.Config
func (r byConfigName) Len() int { return len(r) }
func (r byConfigName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r byConfigName) Less(i, j int) bool {
return sortorder.NaturalLess(r[i].Spec.Name, r[j].Spec.Name)
}
type listOptions struct {
quiet bool
format string
@ -67,8 +55,6 @@ func runConfigList(dockerCli command.Cli, options listOptions) error {
}
}
sort.Sort(byConfigName(configs))
configCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewConfigFormat(format, options.quiet),

View File

@ -50,20 +50,14 @@ func TestConfigList(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
*Config(ConfigID("ID-1-foo"),
ConfigName("1-foo"),
*Config(ConfigID("ID-foo"),
ConfigName("foo"),
ConfigVersion(swarm.Version{Index: 10}),
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
*Config(ConfigID("ID-10-foo"),
ConfigName("10-foo"),
ConfigVersion(swarm.Version{Index: 11}),
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
),
*Config(ConfigID("ID-2-foo"),
ConfigName("2-foo"),
*Config(ConfigID("ID-bar"),
ConfigName("bar"),
ConfigVersion(swarm.Version{Index: 11}),
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
@ -72,8 +66,9 @@ func TestConfigList(t *testing.T) {
},
})
cmd := newConfigListCommand(cli)
cmd.SetOutput(cli.OutBuffer())
assert.NoError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "config-list-sort.golden")
golden.Assert(t, cli.OutBuffer().String(), "config-list.golden")
}
func TestConfigListWithQuietOption(t *testing.T) {

View File

@ -1,4 +0,0 @@
ID NAME CREATED UPDATED
ID-1-foo 1-foo 2 hours ago About an hour ago
ID-2-foo 2-foo 2 hours ago About an hour ago
ID-10-foo 10-foo 2 hours ago About an hour ago

View File

@ -1,2 +1,2 @@
bar label=label-bar
foo
bar label=label-bar

View File

@ -1,3 +1,3 @@
ID NAME CREATED UPDATED
ID-bar bar 2 hours ago About an hour ago
ID-foo foo 2 hours ago About an hour ago
ID-bar bar 2 hours ago About an hour ago

View File

@ -1,2 +1,2 @@
bar label=label-bar
foo
bar label=label-bar

View File

@ -1,2 +1,2 @@
ID-bar
ID-foo
ID-bar

View File

@ -0,0 +1,3 @@
ID NAME CREATED UPDATED
ID-foo foo 2 hours ago About an hour ago
ID-bar bar 2 hours ago About an hour ago

View File

@ -4,13 +4,13 @@ import (
"io"
"net/http/httputil"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/signal"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)

View File

@ -113,7 +113,7 @@ func resolveLocalPath(localPath string) (absPath string, err error) {
return
}
return archive.PreserveTrailingDotOrSeparator(absPath, localPath, filepath.Separator), nil
return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
}
func copyFromContainer(ctx context.Context, dockerCli *command.DockerCli, srcContainer, srcPath, dstPath string, cpParam *cpConfig) (err error) {

View File

@ -4,14 +4,15 @@ import (
"fmt"
"io"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
apiclient "github.com/docker/docker/client"
"github.com/docker/docker/pkg/promise"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
@ -105,6 +106,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *typ
var (
out, stderr io.Writer
in io.ReadCloser
errCh chan error
)
if execConfig.AttachStdin {
@ -127,25 +129,19 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *typ
return err
}
defer resp.Close()
errCh = promise.Go(func() error {
streamer := hijackedIOStreamer{
streams: dockerCli,
inputStream: in,
outputStream: out,
errorStream: stderr,
resp: resp,
tty: execConfig.Tty,
detachKeys: execConfig.DetachKeys,
}
errCh := make(chan error, 1)
go func() {
defer close(errCh)
errCh <- func() error {
streamer := hijackedIOStreamer{
streams: dockerCli,
inputStream: in,
outputStream: out,
errorStream: stderr,
resp: resp,
tty: execConfig.Tty,
detachKeys: execConfig.DetachKeys,
}
return streamer.stream(ctx)
}()
}()
return streamer.stream(ctx)
})
if execConfig.Tty && dockerCli.In().IsTerminal() {
if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil {

View File

@ -6,12 +6,12 @@ import (
"runtime"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
)
@ -185,7 +185,6 @@ func setRawTerminal(streams command.Streams) error {
return streams.Out().SetRawTerminal()
}
// nolint: unparam
func restoreTerminal(streams command.Streams, in io.Closer) error {
streams.In().RestoreTerminal()
streams.Out().RestoreTerminal()

View File

@ -11,6 +11,7 @@ import (
"strings"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
@ -19,7 +20,6 @@ import (
"github.com/docker/docker/pkg/signal"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
)

View File

@ -43,7 +43,6 @@ func TestValidateAttach(t *testing.T) {
}
}
// nolint: unparam
func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
flags.SetOutput(ioutil.Discard)

View File

@ -10,15 +10,16 @@ import (
"strings"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/net/context"
@ -290,27 +291,22 @@ func attachContainer(
return nil, errAttach
}
ch := make(chan error, 1)
*errCh = ch
*errCh = promise.Go(func() error {
streamer := hijackedIOStreamer{
streams: dockerCli,
inputStream: in,
outputStream: out,
errorStream: cerr,
resp: resp,
tty: config.Tty,
detachKeys: options.DetachKeys,
}
go func() {
ch <- func() error {
streamer := hijackedIOStreamer{
streams: dockerCli,
inputStream: in,
outputStream: out,
errorStream: cerr,
resp: resp,
tty: config.Tty,
detachKeys: options.DetachKeys,
}
if errHijack := streamer.stream(ctx); errHijack != nil {
return errHijack
}
return errAttach
}()
}()
if errHijack := streamer.stream(ctx); errHijack != nil {
return errHijack
}
return errAttach
})
return resp.Close, nil
}

View File

@ -9,6 +9,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
"github.com/pkg/errors"
@ -102,28 +103,23 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error {
return errAttach
}
defer resp.Close()
cErr := promise.Go(func() error {
streamer := hijackedIOStreamer{
streams: dockerCli,
inputStream: in,
outputStream: dockerCli.Out(),
errorStream: dockerCli.Err(),
resp: resp,
tty: c.Config.Tty,
detachKeys: options.DetachKeys,
}
cErr := make(chan error, 1)
go func() {
cErr <- func() error {
streamer := hijackedIOStreamer{
streams: dockerCli,
inputStream: in,
outputStream: dockerCli.Out(),
errorStream: dockerCli.Err(),
resp: resp,
tty: c.Config.Tty,
detachKeys: options.DetachKeys,
}
errHijack := streamer.stream(ctx)
if errHijack == nil {
return errAttach
}
return errHijack
}()
}()
errHijack := streamer.stream(ctx)
if errHijack == nil {
return errAttach
}
return errHijack
})
// 3. We should open a channel for receiving status code of the container
// no matter it's detached, removed on daemon side(--rm) or exit normally.

View File

@ -21,7 +21,6 @@ import (
type statsOptions struct {
all bool
noStream bool
noTrunc bool
format string
containers []string
}
@ -43,7 +42,6 @@ func NewStatsCommand(dockerCli *command.DockerCli) *cobra.Command {
flags := cmd.Flags()
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template")
return cmd
}
@ -216,7 +214,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
ccstats = append(ccstats, c.GetStatistics())
}
cStats.mu.Unlock()
if err = formatter.ContainerStatsWrite(statsCtx, ccstats, daemonOSType, !opts.noTrunc); err != nil {
if err = formatter.ContainerStatsWrite(statsCtx, ccstats, daemonOSType); err != nil {
break
}
if len(cStats.cs) == 0 && !showAll {

View File

@ -7,11 +7,11 @@ import (
"sync"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
)

View File

@ -7,11 +7,11 @@ import (
"runtime"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/signal"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
)

View File

@ -3,13 +3,13 @@ package container
import (
"strconv"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/versions"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
)

View File

@ -3,8 +3,8 @@ package command
import (
"sync"
"github.com/Sirupsen/logrus"
eventtypes "github.com/docker/docker/api/types/events"
"github.com/sirupsen/logrus"
)
// EventHandler is abstract interface for user to customize

View File

@ -10,7 +10,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -231,7 +230,10 @@ size: 0B
// Special headers for customized table format
{
Context{Format: NewContainerFormat(`table {{truncate .ID 5}}\t{{json .Image}} {{.RunningFor}}/{{title .Status}}/{{pad .Ports 2 2}}.{{upper .Names}} {{lower .Status}}`, false, true)},
string(golden.Get(t, "container-context-write-special-headers.golden")),
`CONTAINER ID IMAGE CREATED/STATUS/ PORTS .NAMES STATUS
conta "ubuntu" 24 hours ago//.FOOBAR_BAZ
conta "ubuntu" 24 hours ago//.FOOBAR_BAR
`,
},
}

View File

@ -4,7 +4,6 @@ import (
"bytes"
"testing"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/stretchr/testify/assert"
)
@ -84,7 +83,12 @@ Build Cache 0B
Format: NewDiskUsageFormat("table {{.Type}}\t{{.Active}}"),
},
},
string(golden.Get(t, "disk-usage-context-write-custom.golden")),
`TYPE ACTIVE
Images 0
Containers 0
Local Volumes 0
Build Cache
`,
},
// Raw Format
{
@ -93,7 +97,31 @@ Build Cache 0B
Format: NewDiskUsageFormat("raw"),
},
},
string(golden.Get(t, "disk-usage-raw-format.golden")),
`type: Images
total: 0
active: 0
size: 0B
reclaimable: 0B
type: Containers
total: 0
active: 0
size: 0B
reclaimable: 0B
type: Local Volumes
total: 0
active: 0
size: 0B
reclaimable: 0B
type: Build Cache
total:
active:
size: 0B
reclaimable: 0B
`,
},
}

View File

@ -8,7 +8,6 @@ import (
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/stringutils"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/stretchr/testify/assert"
)
@ -121,7 +120,10 @@ func TestSearchContextWrite(t *testing.T) {
// Table format
{
Context{Format: NewSearchFormat("table")},
string(golden.Get(t, "search-context-write-table.golden")),
`NAME DESCRIPTION STARS OFFICIAL AUTOMATED
result1 Official build 5000 [OK]
result2 Not official 5 [OK]
`,
},
{
Context{Format: NewSearchFormat("table {{.Name}}")},
@ -208,7 +210,9 @@ func TestSearchContextWriteStars(t *testing.T) {
// Table format
{
Context{Format: NewSearchFormat("table")},
string(golden.Get(t, "search-context-write-stars-table.golden")),
`NAME DESCRIPTION STARS OFFICIAL AUTOMATED
result1 Official build 5000 [OK]
`,
},
{
Context{Format: NewSearchFormat("table {{.Name}}")},

View File

@ -12,20 +12,19 @@ import (
)
const (
defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.CreatedAt}}\t{{.UpdatedAt}}"
defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CreatedAt}}\t{{.UpdatedAt}}"
secretIDHeader = "ID"
secretCreatedHeader = "CREATED"
secretUpdatedHeader = "UPDATED"
secretInspectPrettyTemplate Format = `ID: {{.ID}}
Name: {{.Name}}
secretInspectPrettyTemplate Format = `ID: {{.ID}}
Name: {{.Name}}
{{- if .Labels }}
Labels:
{{- range $k, $v := .Labels }}
- {{ $k }}{{if $v }}={{ $v }}{{ end }}
{{- end }}{{ end }}
Driver: {{.Driver}}
Created at: {{.CreatedAt}}
Updated at: {{.UpdatedAt}}`
Created at: {{.CreatedAt}}
Updated at: {{.UpdatedAt}}`
)
// NewSecretFormat returns a Format for rendering using a secret Context
@ -62,7 +61,6 @@ func newSecretContext() *secretContext {
sCtx.header = map[string]string{
"ID": secretIDHeader,
"Name": nameHeader,
"Driver": driverHeader,
"CreatedAt": secretCreatedHeader,
"UpdatedAt": secretUpdatedHeader,
"Labels": labelsHeader,
@ -91,13 +89,6 @@ func (c *secretContext) CreatedAt() string {
return units.HumanDuration(time.Now().UTC().Sub(c.s.Meta.CreatedAt)) + " ago"
}
func (c *secretContext) Driver() string {
if c.s.Spec.Driver == nil {
return ""
}
return c.s.Spec.Driver.Name
}
func (c *secretContext) UpdatedAt() string {
return units.HumanDuration(time.Now().UTC().Sub(c.s.Meta.UpdatedAt)) + " ago"
}
@ -162,13 +153,6 @@ func (ctx *secretInspectContext) Labels() map[string]string {
return ctx.Secret.Spec.Labels
}
func (ctx *secretInspectContext) Driver() string {
if ctx.Secret.Spec.Driver == nil {
return ""
}
return ctx.Secret.Spec.Driver.Name
}
func (ctx *secretInspectContext) CreatedAt() string {
return command.PrettyPrint(ctx.Secret.CreatedAt)
}

View File

@ -28,9 +28,9 @@ func TestSecretContextFormatWrite(t *testing.T) {
},
// Table format
{Context{Format: NewSecretFormat("table", false)},
`ID NAME DRIVER CREATED UPDATED
1 passwords Less than a second ago Less than a second ago
2 id_rsa Less than a second ago Less than a second ago
`ID NAME CREATED UPDATED
1 passwords Less than a second ago Less than a second ago
2 id_rsa Less than a second ago Less than a second ago
`},
{Context{Format: NewSecretFormat("table {{.Name}}", true)},
`NAME

View File

@ -504,10 +504,7 @@ func (c *serviceContext) Replicas() string {
}
func (c *serviceContext) Image() string {
var image string
if c.service.Spec.TaskTemplate.ContainerSpec != nil {
image = c.service.Spec.TaskTemplate.ContainerSpec.Image
}
image := c.service.Spec.TaskTemplate.ContainerSpec.Image
if ref, err := reference.ParseNormalizedNamed(image); err == nil {
// update image string for display, (strips any digest)
if nt, ok := ref.(reference.NamedTagged); ok {

View File

@ -8,7 +8,6 @@ import (
"testing"
"github.com/docker/docker/api/types/swarm"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -60,7 +59,21 @@ bar
// Raw Format
{
Context{Format: NewServiceListFormat("raw", false)},
string(golden.Get(t, "service-context-write-raw.golden")),
`id: id_baz
name: baz
mode: global
replicas: 2/4
image:
ports: *:80->8080/tcp
id: id_bar
name: bar
mode: replicated
replicas: 2/4
image:
ports: *:80->8080/tcp
`,
},
{
Context{Format: NewServiceListFormat("raw", true)},

View File

@ -4,14 +4,13 @@ import (
"fmt"
"sync"
"github.com/docker/docker/pkg/stringid"
units "github.com/docker/go-units"
)
const (
winOSType = "windows"
defaultStatsTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}"
winDefaultStatsTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
defaultStatsTableFormat = "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}"
winDefaultStatsTableFormat = "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
containerHeader = "CONTAINER"
cpuPercHeader = "CPU %"
@ -115,13 +114,12 @@ func NewContainerStats(container string) *ContainerStats {
}
// ContainerStatsWrite renders the context for a list of containers statistics
func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string, trunc bool) error {
func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string) error {
render := func(format func(subContext subContext) error) error {
for _, cstats := range containerStats {
containerStatsCtx := &containerStatsContext{
s: cstats,
os: osType,
trunc: trunc,
s: cstats,
os: osType,
}
if err := format(containerStatsCtx); err != nil {
return err
@ -151,9 +149,8 @@ func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string
type containerStatsContext struct {
HeaderContext
s StatsEntry
os string
trunc bool
s StatsEntry
os string
}
func (c *containerStatsContext) MarshalJSON() ([]byte, error) {
@ -172,9 +169,6 @@ func (c *containerStatsContext) Name() string {
}
func (c *containerStatsContext) ID() string {
if c.trunc {
return stringid.TruncateID(c.s.ID)
}
return c.s.ID
}

View File

@ -114,7 +114,7 @@ container2 --
}
var out bytes.Buffer
te.context.Output = &out
err := ContainerStatsWrite(te.context, stats, "linux", false)
err := ContainerStatsWrite(te.context, stats, "linux")
if err != nil {
assert.EqualError(t, err, te.expected)
} else {
@ -180,7 +180,7 @@ container2 -- --
}
var out bytes.Buffer
te.context.Output = &out
err := ContainerStatsWrite(te.context, stats, "windows", false)
err := ContainerStatsWrite(te.context, stats, "windows")
if err != nil {
assert.EqualError(t, err, te.expected)
} else {
@ -220,7 +220,7 @@ func TestContainerStatsContextWriteWithNoStats(t *testing.T) {
}
for _, context := range contexts {
ContainerStatsWrite(context.context, []StatsEntry{}, "linux", false)
ContainerStatsWrite(context.context, []StatsEntry{}, "linux")
assert.Equal(t, context.expected, out.String())
// Clean buffer
out.Reset()
@ -258,41 +258,7 @@ func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
}
for _, context := range contexts {
ContainerStatsWrite(context.context, []StatsEntry{}, "windows", false)
assert.Equal(t, context.expected, out.String())
// Clean buffer
out.Reset()
}
}
func TestContainerStatsContextWriteTrunc(t *testing.T) {
var out bytes.Buffer
contexts := []struct {
context Context
trunc bool
expected string
}{
{
Context{
Format: "{{.ID}}",
Output: &out,
},
false,
"b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc\n",
},
{
Context{
Format: "{{.ID}}",
Output: &out,
},
true,
"b95a83497c91\n",
},
}
for _, context := range contexts {
ContainerStatsWrite(context.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", context.trunc)
ContainerStatsWrite(context.context, []StatsEntry{}, "windows")
assert.Equal(t, context.expected, out.String())
// Clean buffer
out.Reset()

View File

@ -7,7 +7,6 @@ import (
"testing"
"github.com/docker/docker/api/types/swarm"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/stretchr/testify/assert"
)
@ -34,7 +33,10 @@ taskID2
},
{
Context{Format: NewTaskFormat("table {{.Name}}\t{{.Node}}\t{{.Ports}}", false)},
string(golden.Get(t, "task-context-write-table-custom.golden")),
`NAME NODE PORTS
foobar_baz foo1
foobar_bar foo2
`,
},
{
Context{Format: NewTaskFormat("table {{.Name}}", true)},

View File

@ -1,3 +0,0 @@
CONTAINER ID IMAGE CREATED/STATUS/ PORTS .NAMES STATUS
conta "ubuntu" 24 hours ago//.FOOBAR_BAZ
conta "ubuntu" 24 hours ago//.FOOBAR_BAR

View File

@ -1,5 +0,0 @@
TYPE ACTIVE
Images 0
Containers 0
Local Volumes 0
Build Cache

View File

@ -1,24 +0,0 @@
type: Images
total: 0
active: 0
size: 0B
reclaimable: 0B
type: Containers
total: 0
active: 0
size: 0B
reclaimable: 0B
type: Local Volumes
total: 0
active: 0
size: 0B
reclaimable: 0B
type: Build Cache
total:
active:
size: 0B
reclaimable: 0B

View File

@ -1,2 +0,0 @@
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
result1 Official build 5000 [OK]

View File

@ -1,3 +0,0 @@
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
result1 Official build 5000 [OK]
result2 Not official 5 [OK]

View File

@ -1,14 +0,0 @@
id: id_baz
name: baz
mode: global
replicas: 2/4
image:
ports: *:80->8080/tcp
id: id_bar
name: bar
mode: replicated
replicas: 2/4
image:
ports: *:80->8080/tcp

View File

@ -1,3 +0,0 @@
NAME NODE PORTS
foobar_baz foo1
foobar_bar foo2

View File

@ -1,150 +0,0 @@
package formatter
import (
"sort"
"strings"
"github.com/docker/docker/pkg/stringid"
)
const (
defaultTrustTagTableFormat = "table {{.SignedTag}}\t{{.Digest}}\t{{.Signers}}"
signedTagNameHeader = "SIGNED TAG"
trustedDigestHeader = "DIGEST"
signersHeader = "SIGNERS"
defaultSignerInfoTableFormat = "table {{.Signer}}\t{{.Keys}}"
signerNameHeader = "SIGNER"
keysHeader = "KEYS"
)
// SignedTagInfo represents all formatted information needed to describe a signed tag:
// Name: name of the signed tag
// Digest: hex encoded digest of the contents
// Signers: list of entities who signed the tag
type SignedTagInfo struct {
Name string
Digest string
Signers []string
}
// SignerInfo represents all formatted information needed to describe a signer:
// Name: name of the signer role
// Keys: the keys associated with the signer
type SignerInfo struct {
Name string
Keys []string
}
// NewTrustTagFormat returns a Format for rendering using a trusted tag Context
func NewTrustTagFormat() Format {
return defaultTrustTagTableFormat
}
// NewSignerInfoFormat returns a Format for rendering a signer role info Context
func NewSignerInfoFormat() Format {
return defaultSignerInfoTableFormat
}
// TrustTagWrite writes the context
func TrustTagWrite(ctx Context, signedTagInfoList []SignedTagInfo) error {
render := func(format func(subContext subContext) error) error {
for _, signedTag := range signedTagInfoList {
if err := format(&trustTagContext{s: signedTag}); err != nil {
return err
}
}
return nil
}
trustTagCtx := trustTagContext{}
trustTagCtx.header = trustTagHeaderContext{
"SignedTag": signedTagNameHeader,
"Digest": trustedDigestHeader,
"Signers": signersHeader,
}
return ctx.Write(&trustTagCtx, render)
}
type trustTagHeaderContext map[string]string
type trustTagContext struct {
HeaderContext
s SignedTagInfo
}
// SignedTag returns the name of the signed tag
func (c *trustTagContext) SignedTag() string {
return c.s.Name
}
// Digest returns the hex encoded digest associated with this signed tag
func (c *trustTagContext) Digest() string {
return c.s.Digest
}
// Signers returns the sorted list of entities who signed this tag
func (c *trustTagContext) Signers() string {
sort.Strings(c.s.Signers)
return strings.Join(c.s.Signers, ", ")
}
// SignerInfoWrite writes the context
func SignerInfoWrite(ctx Context, signerInfoList []SignerInfo) error {
render := func(format func(subContext subContext) error) error {
for _, signerInfo := range signerInfoList {
if err := format(&signerInfoContext{
trunc: ctx.Trunc,
s: signerInfo,
}); err != nil {
return err
}
}
return nil
}
signerInfoCtx := signerInfoContext{}
signerInfoCtx.header = signerInfoHeaderContext{
"Signer": signerNameHeader,
"Keys": keysHeader,
}
return ctx.Write(&signerInfoCtx, render)
}
type signerInfoHeaderContext map[string]string
type signerInfoContext struct {
HeaderContext
trunc bool
s SignerInfo
}
// Keys returns the sorted list of keys associated with the signer
func (c *signerInfoContext) Keys() string {
sort.Strings(c.s.Keys)
truncatedKeys := []string{}
if c.trunc {
for _, keyID := range c.s.Keys {
truncatedKeys = append(truncatedKeys, stringid.TruncateID(keyID))
}
return strings.Join(truncatedKeys, ", ")
}
return strings.Join(c.s.Keys, ", ")
}
// Signer returns the name of the signer
func (c *signerInfoContext) Signer() string {
return c.s.Name
}
// SignerInfoList helps sort []SignerInfo by signer names
type SignerInfoList []SignerInfo
func (signerInfoComp SignerInfoList) Len() int {
return len(signerInfoComp)
}
func (signerInfoComp SignerInfoList) Less(i, j int) bool {
return signerInfoComp[i].Name < signerInfoComp[j].Name
}
func (signerInfoComp SignerInfoList) Swap(i, j int) {
signerInfoComp[i], signerInfoComp[j] = signerInfoComp[j], signerInfoComp[i]
}

View File

@ -1,238 +0,0 @@
package formatter
import (
"bytes"
"testing"
"github.com/docker/docker/pkg/stringid"
"github.com/stretchr/testify/assert"
)
func TestTrustTag(t *testing.T) {
digest := stringid.GenerateRandomID()
trustedTag := "tag"
var ctx trustTagContext
cases := []struct {
trustTagCtx trustTagContext
expValue string
call func() string
}{
{
trustTagContext{
s: SignedTagInfo{Name: trustedTag,
Digest: digest,
Signers: nil,
},
},
digest,
ctx.Digest,
},
{
trustTagContext{
s: SignedTagInfo{Name: trustedTag,
Digest: digest,
Signers: nil,
},
},
trustedTag,
ctx.SignedTag,
},
// Empty signers makes a row with empty string
{
trustTagContext{
s: SignedTagInfo{Name: trustedTag,
Digest: digest,
Signers: nil,
},
},
"",
ctx.Signers,
},
{
trustTagContext{
s: SignedTagInfo{Name: trustedTag,
Digest: digest,
Signers: []string{"alice", "bob", "claire"},
},
},
"alice, bob, claire",
ctx.Signers,
},
// alphabetic signing on Signers
{
trustTagContext{
s: SignedTagInfo{Name: trustedTag,
Digest: digest,
Signers: []string{"claire", "bob", "alice"},
},
},
"alice, bob, claire",
ctx.Signers,
},
}
for _, c := range cases {
ctx = c.trustTagCtx
v := c.call()
if v != c.expValue {
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
}
}
}
func TestTrustTagContextWrite(t *testing.T) {
cases := []struct {
context Context
expected string
}{
// Errors
{
Context{
Format: "{{InvalidFunction}}",
},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
Context{
Format: "{{nil}}",
},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table Format
{
Context{
Format: NewTrustTagFormat(),
},
`SIGNED TAG DIGEST SIGNERS
tag1 deadbeef alice
tag2 aaaaaaaa alice, bob
tag3 bbbbbbbb
`,
},
}
for _, testcase := range cases {
signedTags := []SignedTagInfo{
{Name: "tag1", Digest: "deadbeef", Signers: []string{"alice"}},
{Name: "tag2", Digest: "aaaaaaaa", Signers: []string{"alice", "bob"}},
{Name: "tag3", Digest: "bbbbbbbb", Signers: []string{}},
}
out := bytes.NewBufferString("")
testcase.context.Output = out
err := TrustTagWrite(testcase.context, signedTags)
if err != nil {
assert.EqualError(t, err, testcase.expected)
} else {
assert.Equal(t, testcase.expected, out.String())
}
}
}
// With no trust data, the TrustTagWrite will print an empty table:
// it's up to the caller to decide whether or not to print this versus an error
func TestTrustTagContextEmptyWrite(t *testing.T) {
emptyCase := struct {
context Context
expected string
}{
Context{
Format: NewTrustTagFormat(),
},
`SIGNED TAG DIGEST SIGNERS
`,
}
emptySignedTags := []SignedTagInfo{}
out := bytes.NewBufferString("")
emptyCase.context.Output = out
err := TrustTagWrite(emptyCase.context, emptySignedTags)
assert.NoError(t, err)
assert.Equal(t, emptyCase.expected, out.String())
}
func TestSignerInfoContextEmptyWrite(t *testing.T) {
emptyCase := struct {
context Context
expected string
}{
Context{
Format: NewSignerInfoFormat(),
},
`SIGNER KEYS
`,
}
emptySignerInfo := []SignerInfo{}
out := bytes.NewBufferString("")
emptyCase.context.Output = out
err := SignerInfoWrite(emptyCase.context, emptySignerInfo)
assert.NoError(t, err)
assert.Equal(t, emptyCase.expected, out.String())
}
func TestSignerInfoContextWrite(t *testing.T) {
cases := []struct {
context Context
expected string
}{
// Errors
{
Context{
Format: "{{InvalidFunction}}",
},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
Context{
Format: "{{nil}}",
},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table Format
{
Context{
Format: NewSignerInfoFormat(),
Trunc: true,
},
`SIGNER KEYS
alice key11, key12
bob key21
eve foobarbazqux, key31, key32
`,
},
// No truncation
{
Context{
Format: NewSignerInfoFormat(),
},
`SIGNER KEYS
alice key11, key12
bob key21
eve foobarbazquxquux, key31, key32
`,
},
}
for _, testcase := range cases {
signerInfo := SignerInfoList{
{Name: "alice", Keys: []string{"key11", "key12"}},
{Name: "bob", Keys: []string{"key21"}},
{Name: "eve", Keys: []string{"key31", "key32", "foobarbazquxquux"}},
}
out := bytes.NewBufferString("")
testcase.context.Output = out
err := SignerInfoWrite(testcase.context, signerInfo)
if err != nil {
assert.EqualError(t, err, testcase.expected)
} else {
assert.Equal(t, testcase.expected, out.String())
}
}
}

View File

@ -12,6 +12,7 @@ import (
"regexp"
"runtime"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image/build"
@ -21,14 +22,12 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/urlutil"
units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
@ -244,7 +243,6 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
ExcludePatterns: excludes,
ChownOpts: &idtools.IDPair{UID: 0, GID: 0},
})
if err != nil {
return err
@ -378,13 +376,13 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
if s != nil {
go func() {
logrus.Debugf("running session: %v", s.ID())
logrus.Debugf("running session: %v", s.UUID())
if err := s.Run(ctx, dockerCli.Client().DialSession); err != nil {
logrus.Error(err)
cancel() // cancel progress context
}
}()
buildOptions.SessionID = s.ID()
buildOptions.SessionID = s.UUID()
}
response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)

View File

@ -17,9 +17,9 @@ import (
"github.com/docker/cli/cli/command/image/build"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client/session"
"github.com/docker/docker/client/session/filesync"
"github.com/docker/docker/pkg/progress"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/filesync"
"github.com/pkg/errors"
"golang.org/x/time/rate"
)
@ -53,9 +53,7 @@ func addDirToSession(session *session.Session, contextDir string, progressOutput
p := &sizeProgress{out: progressOutput, action: "Streaming build context to Docker daemon"}
workdirProvider := filesync.NewFSSyncProvider([]filesync.SyncedDir{
{Dir: contextDir, Excludes: excludes},
})
workdirProvider := filesync.NewFSSyncProvider(contextDir, excludes)
session.Allow(workdirProvider)
// this will be replaced on parallel build jobs. keep the current

View File

@ -6,57 +6,18 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sort"
"syscall"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive"
"github.com/gotestyourself/gotestyourself/fs"
"github.com/gotestyourself/gotestyourself/skip"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestRunBuildResetsUidAndGidInContext(t *testing.T) {
skip.IfCondition(t, runtime.GOOS == "windows", "uid and gid not relevant on windows")
dest := fs.NewDir(t, "test-build-context-dest")
defer dest.Remove()
fakeImageBuild := func(_ context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
assert.NoError(t, archive.Untar(context, dest.Path(), nil))
body := new(bytes.Buffer)
return types.ImageBuildResponse{Body: ioutil.NopCloser(body)}, nil
}
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild})
dir := fs.NewDir(t, "test-build-context",
fs.WithFile("foo", "some content", fs.AsUser(65534, 65534)),
fs.WithFile("Dockerfile", `
FROM alpine:3.6
COPY foo bar /
`),
)
defer dir.Remove()
options := newBuildOptions()
options.context = dir.Path()
err := runBuild(cli, options)
require.NoError(t, err)
files, err := ioutil.ReadDir(dest.Path())
require.NoError(t, err)
for _, fileInfo := range files {
assert.Equal(t, uint32(0), fileInfo.Sys().(*syscall.Stat_t).Uid)
assert.Equal(t, uint32(0), fileInfo.Sys().(*syscall.Stat_t).Gid)
}
}
func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
dest, err := ioutil.TempDir("", "test-build-compress-dest")
require.NoError(t, err)

View File

@ -6,8 +6,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
"github.com/docker/distribution/reference"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -40,32 +40,39 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runPull(cli command.Cli, opts pullOptions) error {
func runPull(dockerCli command.Cli, opts pullOptions) error {
distributionRef, err := reference.ParseNormalizedNamed(opts.remote)
switch {
case err != nil:
if err != nil {
return err
case opts.all && !reference.IsNameOnly(distributionRef):
}
if opts.all && !reference.IsNameOnly(distributionRef) {
return errors.New("tag can't be used with --all-tags/-a")
case !opts.all && reference.IsNameOnly(distributionRef):
}
if !opts.all && reference.IsNameOnly(distributionRef) {
distributionRef = reference.TagNameOnly(distributionRef)
if tagged, ok := distributionRef.(reference.Tagged); ok {
fmt.Fprintf(cli.Out(), "Using default tag: %s\n", tagged.Tag())
fmt.Fprintf(dockerCli.Out(), "Using default tag: %s\n", tagged.Tag())
}
}
ctx := context.Background()
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, AuthResolver(cli), distributionRef.String())
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(distributionRef)
if err != nil {
return err
}
ctx := context.Background()
authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "pull")
// Check if reference has a digest
_, isCanonical := distributionRef.(reference.Canonical)
if command.IsTrusted() && !isCanonical {
err = trustedPull(ctx, cli, imgRefAndAuth)
err = trustedPull(ctx, dockerCli, repoInfo, distributionRef, authConfig, requestPrivilege)
} else {
err = imagePullPrivileged(ctx, cli, imgRefAndAuth, opts.all)
err = imagePullPrivileged(ctx, dockerCli, authConfig, reference.FamiliarString(distributionRef), requestPrivilege, opts.all)
}
if err != nil {
if strings.Contains(err.Error(), "when fetching 'plugin'") {
@ -73,5 +80,6 @@ func runPull(cli command.Cli, opts pullOptions) error {
}
return err
}
return nil
}

View File

@ -2,14 +2,11 @@ package image
import (
"fmt"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/testutil"
"github.com/docker/docker/api/types"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/stretchr/testify/assert"
)
@ -35,6 +32,11 @@ func TestNewPullCommandErrors(t *testing.T) {
expectedError: "tag can't be used with --all-tags/-a",
args: []string{"--all-tags", "image:tag"},
},
{
name: "pull-error",
args: []string{"--disable-content-trust=false", "image:tag"},
expectedError: "you are not authorized to perform this operation: server returned 401.",
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{})
@ -47,28 +49,20 @@ func TestNewPullCommandErrors(t *testing.T) {
func TestNewPullCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
expectedTag string
name string
args []string
}{
{
name: "simple",
args: []string{"image:tag"},
expectedTag: "image:tag",
name: "simple",
args: []string{"image:tag"},
},
{
name: "simple-no-tag",
args: []string{"image"},
expectedTag: "image:latest",
name: "simple-no-tag",
args: []string{"image"},
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{
imagePullFunc: func(ref string, options types.ImagePullOptions) (io.ReadCloser, error) {
assert.Equal(t, tc.expectedTag, ref, tc.name)
return ioutil.NopCloser(strings.NewReader("")), nil
},
})
cli := test.NewFakeCli(&fakeClient{})
cmd := NewPullCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)

View File

@ -48,7 +48,7 @@ func runPush(dockerCli command.Cli, remote string) error {
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push")
if command.IsTrusted() {
return TrustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege)
return trustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege)
}
responseBody, err := imagePushPrivileged(ctx, dockerCli, authConfig, ref, requestPrivilege)

View File

@ -5,20 +5,20 @@ import (
"encoding/json"
"fmt"
"io"
"path"
"sort"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/registry"
"github.com/docker/notary/client"
"github.com/docker/notary/tuf/data"
digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
)
@ -28,8 +28,8 @@ type target struct {
size int64
}
// TrustedPush handles content trust pushing of an image
func TrustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
// trustedPush handles content trust pushing of an image
func trustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege)
if err != nil {
return err
@ -103,25 +103,25 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
fmt.Fprintln(streams.Out(), "Signing and pushing trust metadata")
repo, err := trust.GetNotaryRepository(streams.In(), streams.Out(), command.UserAgent(), repoInfo, &authConfig, "push", "pull")
repo, err := trust.GetNotaryRepository(streams, repoInfo, authConfig, "push", "pull")
if err != nil {
fmt.Fprintf(streams.Out(), "Error establishing connection to notary repository: %s\n", err)
return err
}
// get the latest repository metadata so we can figure out which roles to sign
_, err = repo.ListTargets()
err = repo.Update(false)
switch err.(type) {
case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
keys := repo.GetCryptoService().ListKeys(data.CanonicalRootRole)
keys := repo.CryptoService.ListKeys(data.CanonicalRootRole)
var rootKeyID string
// always select the first root key
if len(keys) > 0 {
sort.Strings(keys)
rootKeyID = keys[0]
} else {
rootPublicKey, err := repo.GetCryptoService().Create(data.CanonicalRootRole, "", data.ECDSAKey)
rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey)
if err != nil {
return err
}
@ -136,7 +136,7 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
err = repo.AddTarget(target, data.CanonicalTargetsRole)
case nil:
// already initialized and we have successfully downloaded the latest metadata
err = AddTargetToAllSignableRoles(repo, target)
err = addTargetToAllSignableRoles(repo, target)
default:
return trust.NotaryError(repoInfo.Name.Name(), err)
}
@ -154,16 +154,51 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
return nil
}
// AddTargetToAllSignableRoles attempts to add the image target to all the top level delegation roles we can
// Attempt to add the image target to all the top level delegation roles we can
// (based on whether we have the signing key and whether the role's path allows
// us to).
// If there are no delegation roles, we add to the targets role.
func AddTargetToAllSignableRoles(repo client.Repository, target *client.Target) error {
signableRoles, err := trust.GetSignableRoles(repo, target)
func addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.Target) error {
var signableRoles []string
// translate the full key names, which includes the GUN, into just the key IDs
allCanonicalKeyIDs := make(map[string]struct{})
for fullKeyID := range repo.CryptoService.ListAllKeys() {
allCanonicalKeyIDs[path.Base(fullKeyID)] = struct{}{}
}
allDelegationRoles, err := repo.GetDelegationRoles()
if err != nil {
return err
}
// if there are no delegation roles, then just try to sign it into the targets role
if len(allDelegationRoles) == 0 {
return repo.AddTarget(target, data.CanonicalTargetsRole)
}
// there are delegation roles, find every delegation role we have a key for, and
// attempt to sign into into all those roles.
for _, delegationRole := range allDelegationRoles {
// We do not support signing any delegation role that isn't a direct child of the targets role.
// Also don't bother checking the keys if we can't add the target
// to this role due to path restrictions
if path.Dir(delegationRole.Name) != data.CanonicalTargetsRole || !delegationRole.CheckPaths(target.Name) {
continue
}
for _, canonicalKeyID := range delegationRole.KeyIDs {
if _, ok := allCanonicalKeyIDs[canonicalKeyID]; ok {
signableRoles = append(signableRoles, delegationRole.Name)
break
}
}
}
if len(signableRoles) == 0 {
return errors.Errorf("no valid signing keys for delegation roles")
}
return repo.AddTarget(target, signableRoles...)
}
@ -182,13 +217,57 @@ func imagePushPrivileged(ctx context.Context, cli command.Cli, authConfig types.
}
// trustedPull handles content trust pulling of an image
func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth) error {
refs, err := getTrustedPullTargets(cli, imgRefAndAuth)
func trustedPull(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
var refs []target
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
if err != nil {
fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
return err
}
ref := imgRefAndAuth.Reference()
if tagged, isTagged := ref.(reference.NamedTagged); !isTagged {
// List all targets
targets, err := notaryRepo.ListTargets(trust.ReleasesRole, data.CanonicalTargetsRole)
if err != nil {
return trust.NotaryError(ref.Name(), err)
}
for _, tgt := range targets {
t, err := convertTarget(tgt.Target)
if err != nil {
fmt.Fprintf(cli.Out(), "Skipping target for %q\n", reference.FamiliarName(ref))
continue
}
// Only list tags in the top level targets role or the releases delegation role - ignore
// all other delegation roles
if tgt.Role != trust.ReleasesRole && tgt.Role != data.CanonicalTargetsRole {
continue
}
refs = append(refs, t)
}
if len(refs) == 0 {
return trust.NotaryError(ref.Name(), errors.Errorf("No trusted tags for %s", ref.Name()))
}
} else {
t, err := notaryRepo.GetTargetByName(tagged.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
if err != nil {
return trust.NotaryError(ref.Name(), err)
}
// Only get the tag if it's in the top level targets role or the releases delegation role
// ignore it if it's in any other delegation roles
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
return trust.NotaryError(ref.Name(), errors.Errorf("No trust data for %s", tagged.Tag()))
}
logrus.Debugf("retrieving target for %s role\n", t.Role)
r, err := convertTarget(t.Target)
if err != nil {
return err
}
refs = append(refs, r)
}
for i, r := range refs {
displayTag := r.name
if displayTag != "" {
@ -200,11 +279,7 @@ func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.Image
if err != nil {
return err
}
updatedImgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, AuthResolver(cli), trustedRef.String())
if err != nil {
return err
}
if err := imagePullPrivileged(ctx, cli, updatedImgRefAndAuth, false); err != nil {
if err := imagePullPrivileged(ctx, cli, authConfig, reference.FamiliarString(trustedRef), requestPrivilege, false); err != nil {
return err
}
@ -220,65 +295,13 @@ func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.Image
return nil
}
func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth) ([]target, error) {
notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly)
if err != nil {
fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
return nil, err
}
ref := imgRefAndAuth.Reference()
tagged, isTagged := ref.(reference.NamedTagged)
if !isTagged {
// List all targets
targets, err := notaryRepo.ListTargets(trust.ReleasesRole, data.CanonicalTargetsRole)
if err != nil {
return nil, trust.NotaryError(ref.Name(), err)
}
var refs []target
for _, tgt := range targets {
t, err := convertTarget(tgt.Target)
if err != nil {
fmt.Fprintf(cli.Out(), "Skipping target for %q\n", reference.FamiliarName(ref))
continue
}
// Only list tags in the top level targets role or the releases delegation role - ignore
// all other delegation roles
if tgt.Role != trust.ReleasesRole && tgt.Role != data.CanonicalTargetsRole {
continue
}
refs = append(refs, t)
}
if len(refs) == 0 {
return nil, trust.NotaryError(ref.Name(), errors.Errorf("No trusted tags for %s", ref.Name()))
}
return refs, nil
}
t, err := notaryRepo.GetTargetByName(tagged.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
if err != nil {
return nil, trust.NotaryError(ref.Name(), err)
}
// Only get the tag if it's in the top level targets role or the releases delegation role
// ignore it if it's in any other delegation roles
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
return nil, trust.NotaryError(ref.Name(), errors.Errorf("No trust data for %s", tagged.Tag()))
}
logrus.Debugf("retrieving target for %s role\n", t.Role)
r, err := convertTarget(t.Target)
return []target{r}, err
}
// imagePullPrivileged pulls the image and displays it to the output
func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, all bool) error {
ref := reference.FamiliarString(imgRefAndAuth.Reference())
func imagePullPrivileged(ctx context.Context, cli command.Cli, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error {
encodedAuth, err := command.EncodeAuthToBase64(*imgRefAndAuth.AuthConfig())
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
if err != nil {
return err
}
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull")
options := types.ImagePullOptions{
RegistryAuth: encodedAuth,
PrivilegeFunc: requestPrivilege,
@ -312,7 +335,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
// Resolve the Auth config relevant for this server
authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
notaryRepo, err := trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), repoInfo, &authConfig, "pull")
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
if err != nil {
fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
return nil, err
@ -325,7 +348,7 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
// Only list tags in the top level targets role or the releases delegation role - ignore
// all other delegation roles
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
return nil, trust.NotaryError(repoInfo.Name.Name(), client.ErrNoSuchTarget(ref.Tag()))
return nil, trust.NotaryError(repoInfo.Name.Name(), errors.Errorf("No trust data for %s", ref.Tag()))
}
r, err := convertTarget(t.Target)
if err != nil {
@ -359,10 +382,3 @@ func TagTrusted(ctx context.Context, cli command.Cli, trustedRef reference.Canon
return cli.Client().ImageTag(ctx, trustedFamiliarRef, familiarRef)
}
// AuthResolver returns an auth resolver function from a command.Cli
func AuthResolver(cli command.Cli) func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
return func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
return command.ResolveAuthConfig(ctx, cli, index)
}
}

View File

@ -1,18 +1,12 @@
package image
import (
"io/ioutil"
"os"
"testing"
"github.com/docker/cli/cli/trust"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/registry"
"github.com/docker/notary/client"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/trustpinning"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func unsetENV() {
@ -61,15 +55,3 @@ func TestNonOfficialTrustServer(t *testing.T) {
t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
}
}
func TestAddTargetToAllSignableRolesError(t *testing.T) {
tmpDir, err := ioutil.TempDir("", "notary-test-")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)
notaryRepo, err := client.NewFileCachedRepository(tmpDir, "gun", "https://localhost", nil, passphrase.ConstantRetriever("password"), trustpinning.TrustPinConfig{})
require.NoError(t, err)
target := client.Target{}
err = AddTargetToAllSignableRoles(notaryRepo, &target)
assert.EqualError(t, err, "client is offline")
}

View File

@ -7,10 +7,10 @@ import (
"strings"
"text/template"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/templates"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Inspector defines an interface to implement to process elements

View File

@ -12,7 +12,6 @@ type fakeClient struct {
networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
networkConnectFunc func(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
networkDisconnectFunc func(ctx context.Context, networkID, container string, force bool) error
networkListFunc func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
}
func (c *fakeClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
@ -35,10 +34,3 @@ func (c *fakeClient) NetworkDisconnect(ctx context.Context, networkID, container
}
return nil
}
func (c *fakeClient) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
if c.networkListFunc != nil {
return c.networkListFunc(ctx, options)
}
return []types.NetworkResource{}, nil
}

View File

@ -1,69 +0,0 @@
package network
import (
"testing"
"io/ioutil"
"strings"
"github.com/docker/cli/internal/test"
. "github.com/docker/cli/internal/test/builders"
"github.com/docker/cli/internal/test/testutil"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestNetworkListErrors(t *testing.T) {
testCases := []struct {
networkListFunc func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
expectedError string
}{
{
networkListFunc: func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
return []types.NetworkResource{}, errors.Errorf("error creating network")
},
expectedError: "error creating network",
},
}
for _, tc := range testCases {
cmd := newListCommand(
test.NewFakeCli(&fakeClient{
networkListFunc: tc.networkListFunc,
}),
)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNetworkListWithFlags(t *testing.T) {
filterArgs := filters.NewArgs()
filterArgs.Add("image.name", "ubuntu")
expectedOpts := types.NetworkListOptions{
Filters: filterArgs,
}
cli := test.NewFakeCli(&fakeClient{
networkListFunc: func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
assert.Equal(t, expectedOpts, options, "not expected options error")
return []types.NetworkResource{*NetworkResource(NetworkResourceID("123454321"),
NetworkResourceName("network_1"),
NetworkResourceDriver("09.7.01"),
NetworkResourceScope("global"))}, nil
},
})
cmd := newListCommand(cli)
cmd.Flags().Set("filter", "image.name=ubuntu")
assert.NoError(t, cmd.Execute())
golden.Assert(t, strings.TrimSpace(cli.OutBuffer().String()), "network-list.golden")
}

View File

@ -1,2 +0,0 @@
NETWORK ID NAME DRIVER SCOPE
123454321 network_1 09.7.01 global

View File

@ -4,8 +4,8 @@ import (
"io"
"os"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/term"
"github.com/sirupsen/logrus"
)
// OutStream is an output stream used by the DockerCli to write normal program

View File

@ -7,13 +7,13 @@ import (
"os"
"path/filepath"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)

View File

@ -65,12 +65,10 @@ func (s pluginRegistryService) ResolveRepository(name reference.Named) (repoInfo
return
}
func newRegistryService() (registry.Service, error) {
svc, err := registry.NewService(registry.ServiceOptions{V2Only: true})
if err != nil {
return nil, err
func newRegistryService() registry.Service {
return pluginRegistryService{
Service: registry.NewService(registry.ServiceOptions{V2Only: true}),
}
return pluginRegistryService{Service: svc}, nil
}
func buildPullConfig(ctx context.Context, dockerCli *command.DockerCli, opts pluginOptions, cmdName string) (types.PluginInstallOptions, error) {
@ -98,11 +96,7 @@ func buildPullConfig(ctx context.Context, dockerCli *command.DockerCli, opts plu
}
ctx := context.Background()
svc, err := newRegistryService()
if err != nil {
return types.PluginInstallOptions{}, err
}
trusted, err := image.TrustedReference(ctx, dockerCli, nt, svc)
trusted, err := image.TrustedReference(ctx, dockerCli, nt, newRegistryService())
if err != nil {
return types.PluginInstallOptions{}, err
}

View File

@ -17,7 +17,6 @@ import (
type createOptions struct {
name string
driver string
file string
labels opts.ListOpts
}
@ -28,21 +27,17 @@ func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command {
}
cmd := &cobra.Command{
Use: "create [OPTIONS] SECRET [file|-]",
Use: "create [OPTIONS] SECRET file|-",
Short: "Create a secret from a file or STDIN as content",
Args: cli.RequiresRangeArgs(1, 2),
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
options.name = args[0]
if len(args) == 2 {
options.file = args[1]
}
options.file = args[1]
return runSecretCreate(dockerCli, options)
},
}
flags := cmd.Flags()
flags.VarP(&options.labels, "label", "l", "Secret labels")
flags.StringVarP(&options.driver, "driver", "d", "", "Secret driver")
flags.SetAnnotation("driver", "version", []string{"1.31"})
return cmd
}
@ -51,14 +46,21 @@ func runSecretCreate(dockerCli command.Cli, options createOptions) error {
client := dockerCli.Client()
ctx := context.Background()
if options.driver != "" && options.file != "" {
return errors.Errorf("When using secret driver secret data must be empty")
var in io.Reader = dockerCli.In()
if options.file != "-" {
file, err := system.OpenSequential(options.file)
if err != nil {
return err
}
in = file
defer file.Close()
}
secretData, err := readSecretData(dockerCli.In(), options.file)
secretData, err := ioutil.ReadAll(in)
if err != nil {
return errors.Errorf("Error reading content from %q: %v", options.file, err)
}
spec := swarm.SecretSpec{
Annotations: swarm.Annotations{
Name: options.name,
@ -66,11 +68,6 @@ func runSecretCreate(dockerCli command.Cli, options createOptions) error {
},
Data: secretData,
}
if options.driver != "" {
spec.Driver = &swarm.Driver{
Name: options.driver,
}
}
r, err := client.SecretCreate(ctx, spec)
if err != nil {
@ -80,23 +77,3 @@ func runSecretCreate(dockerCli command.Cli, options createOptions) error {
fmt.Fprintln(dockerCli.Out(), r.ID)
return nil
}
func readSecretData(in io.ReadCloser, file string) ([]byte, error) {
// Read secret value from external driver
if file == "" {
return nil, nil
}
if file != "-" {
var err error
in, err = system.OpenSequential(file)
if err != nil {
return nil, err
}
defer in.Close()
}
data, err := ioutil.ReadAll(in)
if err != nil {
return nil, err
}
return data, nil
}

View File

@ -24,11 +24,12 @@ func TestSecretCreateErrors(t *testing.T) {
secretCreateFunc func(swarm.SecretSpec) (types.SecretCreateResponse, error)
expectedError string
}{
{args: []string{"too", "many", "arguments"},
expectedError: "requires at least 1 and at most 2 arguments",
{
args: []string{"too_few"},
expectedError: "requires exactly 2 arguments",
},
{args: []string{"create", "--driver", "driver", "-"},
expectedError: "secret data must be empty",
{args: []string{"too", "many", "arguments"},
expectedError: "requires exactly 2 arguments",
},
{
args: []string{"name", filepath.Join("testdata", secretDataFile)},
@ -74,35 +75,6 @@ func TestSecretCreateWithName(t *testing.T) {
assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String()))
}
func TestSecretCreateWithDriver(t *testing.T) {
expectedDriver := &swarm.Driver{
Name: "secret-driver",
}
name := "foo"
cli := test.NewFakeCli(&fakeClient{
secretCreateFunc: func(spec swarm.SecretSpec) (types.SecretCreateResponse, error) {
if spec.Name != name {
return types.SecretCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name)
}
if !reflect.DeepEqual(spec.Driver.Name, expectedDriver.Name) {
return types.SecretCreateResponse{}, errors.Errorf("expected driver %v, got %v", expectedDriver, spec.Labels)
}
return types.SecretCreateResponse{
ID: "ID-" + spec.Name,
}, nil
},
})
cmd := newSecretCreateCommand(cli)
cmd.SetArgs([]string{name})
cmd.Flags().Set("driver", expectedDriver.Name)
assert.NoError(t, cmd.Execute())
assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String()))
}
func TestSecretCreateWithLabels(t *testing.T) {
expectedLabels := map[string]string{
"lbl1": "Label-foo",

View File

@ -154,7 +154,6 @@ func TestSecretInspectPretty(t *testing.T) {
}),
SecretID("secretID"),
SecretName("secretName"),
SecretDriver("driver"),
SecretCreatedAt(time.Time{}),
SecretUpdatedAt(time.Time{}),
), []byte{}, nil

View File

@ -1,27 +1,15 @@
package secret
import (
"sort"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/spf13/cobra"
"golang.org/x/net/context"
"vbom.ml/util/sortorder"
)
type bySecretName []swarm.Secret
func (r bySecretName) Len() int { return len(r) }
func (r bySecretName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r bySecretName) Less(i, j int) bool {
return sortorder.NaturalLess(r[i].Spec.Name, r[j].Spec.Name)
}
type listOptions struct {
quiet bool
format string
@ -65,9 +53,6 @@ func runSecretList(dockerCli command.Cli, options listOptions) error {
format = formatter.TableFormatKey
}
}
sort.Sort(bySecretName(secrets))
secretCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewSecretFormat(format, options.quiet),

View File

@ -1,6 +1,7 @@
package secret
import (
"bytes"
"io/ioutil"
"testing"
"time"
@ -47,35 +48,29 @@ func TestSecretListErrors(t *testing.T) {
}
func TestSecretList(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) {
return []swarm.Secret{
*Secret(SecretID("ID-1-foo"),
SecretName("1-foo"),
*Secret(SecretID("ID-foo"),
SecretName("foo"),
SecretVersion(swarm.Version{Index: 10}),
SecretCreatedAt(time.Now().Add(-2*time.Hour)),
SecretUpdatedAt(time.Now().Add(-1*time.Hour)),
),
*Secret(SecretID("ID-10-foo"),
SecretName("10-foo"),
*Secret(SecretID("ID-bar"),
SecretName("bar"),
SecretVersion(swarm.Version{Index: 11}),
SecretCreatedAt(time.Now().Add(-2*time.Hour)),
SecretUpdatedAt(time.Now().Add(-1*time.Hour)),
SecretDriver("driver"),
),
*Secret(SecretID("ID-2-foo"),
SecretName("2-foo"),
SecretVersion(swarm.Version{Index: 11}),
SecretCreatedAt(time.Now().Add(-2*time.Hour)),
SecretUpdatedAt(time.Now().Add(-1*time.Hour)),
SecretDriver("driver"),
),
}, nil
},
})
cmd := newSecretListCommand(cli)
cmd.SetOutput(buf)
assert.NoError(t, cmd.Execute())
golden.Assert(t, cli.OutBuffer().String(), "secret-list-sort.golden")
golden.Assert(t, cli.OutBuffer().String(), "secret-list.golden")
}
func TestSecretListWithQuietOption(t *testing.T) {

View File

@ -1,7 +1,6 @@
ID: secretID
Name: secretName
ID: secretID
Name: secretName
Labels:
- lbl1=value1
Driver: driver
Created at: 0001-01-01 00:00:00 +0000 utc
Updated at: 0001-01-01 00:00:00 +0000 utc
Created at: 0001-01-01 00:00:00 +0000 utc
Updated at: 0001-01-01 00:00:00 +0000 utc

View File

@ -1,4 +0,0 @@
ID NAME DRIVER CREATED UPDATED
ID-1-foo 1-foo 2 hours ago About an hour ago
ID-2-foo 2-foo driver 2 hours ago About an hour ago
ID-10-foo 10-foo driver 2 hours ago About an hour ago

View File

@ -1,2 +1,2 @@
bar label=label-bar
foo
bar label=label-bar

View File

@ -1,3 +1,3 @@
ID NAME DRIVER CREATED UPDATED
ID-bar bar 2 hours ago About an hour ago
ID-foo foo 2 hours ago About an hour ago
ID NAME CREATED UPDATED
ID-foo foo 2 hours ago About an hour ago
ID-bar bar 2 hours ago About an hour ago

View File

@ -1,2 +1,2 @@
bar label=label-bar
foo
bar label=label-bar

View File

@ -1,2 +1,2 @@
ID-bar
ID-foo
ID-bar

View File

@ -0,0 +1,3 @@
ID NAME CREATED UPDATED
ID-foo foo 2 hours ago About an hour ago
ID-bar bar 2 hours ago About an hour ago

View File

@ -123,7 +123,8 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *serviceOptions
fmt.Fprintf(dockerCli.Out(), "%s\n", response.ID)
if opts.detach || versions.LessThan(apiClient.ClientVersion(), "1.29") {
if opts.detach {
warnDetachDefault(dockerCli.Err(), apiClient.ClientVersion(), flags, "created")
return nil
}

View File

@ -1,12 +1,15 @@
package service
import (
"fmt"
"io"
"io/ioutil"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/service/progress"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/spf13/pflag"
"golang.org/x/net/context"
)
@ -31,3 +34,11 @@ func waitOnService(ctx context.Context, dockerCli command.Cli, serviceID string,
}
return err
}
// warnDetachDefault warns about the --detach flag future change if it's supported.
func warnDetachDefault(err io.Writer, clientVersion string, flags *pflag.FlagSet, msg string) {
if !flags.Changed("detach") && versions.GreaterThanOrEqualTo(clientVersion, "1.29") {
fmt.Fprintf(err, "Since --detach=false was not specified, tasks will be %s in the background.\n"+
"In a future release, --detach=false will become the default.\n", msg)
}
}

View File

@ -0,0 +1,40 @@
package service
import (
"bytes"
"testing"
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
)
func TestWarnDetachDefault(t *testing.T) {
var detach bool
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
addDetachFlag(flags, &detach)
var tests = []struct {
detach bool
version string
expectWarning bool
}{
{true, "1.28", false},
{true, "1.29", false},
{false, "1.28", false},
{false, "1.29", true},
}
for _, test := range tests {
out := new(bytes.Buffer)
flags.Lookup(flagDetach).Changed = test.detach
warnDetachDefault(out, test.version, flags, "")
if test.expectWarning {
assert.NotEmpty(t, out.String(), "expected warning")
} else {
assert.Empty(t, out.String(), "expected no warning")
}
}
}

View File

@ -41,7 +41,7 @@ func formatServiceInspect(t *testing.T, format formatter.Format, now time.Time)
Labels: map[string]string{"com.label": "foo"},
},
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{
ContainerSpec: swarm.ContainerSpec{
Image: "foo/bar@sha256:this_is_a_test",
},
Networks: []swarm.NetworkAttachmentConfig{

View File

@ -13,7 +13,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/service/logs"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
@ -258,7 +257,7 @@ func (lw *logWriter) Write(buf []byte) (int, error) {
return 0, errors.Errorf("invalid context in log message: %v", string(buf))
}
// parse the details out
details, err := logs.ParseLogDetails(string(parts[detailsIndex]))
details, err := client.ParseLogDetails(string(parts[detailsIndex]))
if err != nil {
return 0, err
}

View File

@ -592,7 +592,7 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
},
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{
ContainerSpec: swarm.ContainerSpec{
Image: options.image,
Args: options.args,
Command: options.entrypoint.Value(),
@ -691,7 +691,7 @@ func buildServiceDefaultFlagMapping() flagDefaults {
}
func addDetachFlag(flags *pflag.FlagSet, detach *bool) {
flags.BoolVarP(detach, flagDetach, "d", false, "Exit immediately instead of waiting for the service to converge")
flags.BoolVarP(detach, flagDetach, "d", true, "Exit immediately instead of waiting for the service to converge")
flags.SetAnnotation(flagDetach, "version", []string{"1.29"})
}

View File

@ -7,8 +7,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
func newRollbackCommand(dockerCli command.Cli) *cobra.Command {
@ -19,7 +19,7 @@ func newRollbackCommand(dockerCli command.Cli) *cobra.Command {
Short: "Revert changes to a service's configuration",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runRollback(dockerCli, options, args[0])
return runRollback(dockerCli, cmd.Flags(), options, args[0])
},
Tags: map[string]string{"version": "1.31"},
}
@ -31,7 +31,7 @@ func newRollbackCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runRollback(dockerCli command.Cli, options *serviceOptions, serviceID string) error {
func runRollback(dockerCli command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
apiClient := dockerCli.Client()
ctx := context.Background()
@ -56,7 +56,8 @@ func runRollback(dockerCli command.Cli, options *serviceOptions, serviceID strin
fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
if options.detach || versions.LessThan(apiClient.ClientVersion(), "1.29") {
if options.detach {
warnDetachDefault(dockerCli.Err(), apiClient.ClientVersion(), flags, "rolled back")
return nil
}

View File

@ -10,9 +10,9 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
type scaleOptions struct {
@ -27,7 +27,7 @@ func newScaleCommand(dockerCli command.Cli) *cobra.Command {
Short: "Scale one or multiple replicated services",
Args: scaleArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runScale(dockerCli, options, args)
return runScale(dockerCli, cmd.Flags(), options, args)
},
}
@ -54,7 +54,7 @@ func scaleArgs(cmd *cobra.Command, args []string) error {
return nil
}
func runScale(dockerCli command.Cli, options *scaleOptions, args []string) error {
func runScale(dockerCli command.Cli, flags *pflag.FlagSet, options *scaleOptions, args []string) error {
var errs []string
var serviceIDs []string
ctx := context.Background()
@ -79,7 +79,9 @@ func runScale(dockerCli command.Cli, options *scaleOptions, args []string) error
}
if len(serviceIDs) > 0 {
if !options.detach && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.29") {
if options.detach {
warnDetachDefault(dockerCli.Err(), dockerCli.Client().ClientVersion(), flags, "scaled")
} else {
for _, serviceID := range serviceIDs {
if err := waitOnService(ctx, dockerCli, serviceID, false); err != nil {
errs = append(errs, fmt.Sprintf("%s: %v", serviceID, err))

View File

@ -3,6 +3,7 @@ package service
import (
"encoding/hex"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
"github.com/docker/distribution/reference"
@ -11,7 +12,6 @@ import (
"github.com/docker/notary/tuf/data"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
)
@ -59,7 +59,7 @@ func trustedResolveDigest(ctx context.Context, cli command.Cli, ref reference.Na
authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
notaryRepo, err := trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), repoInfo, &authConfig, "pull")
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
if err != nil {
return nil, errors.Wrap(err, "error establishing connection to trust repository")
}

View File

@ -216,7 +216,8 @@ func runUpdate(dockerCli command.Cli, flags *pflag.FlagSet, options *serviceOpti
fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
if options.detach || versions.LessThan(apiClient.ClientVersion(), "1.29") {
if options.detach {
warnDetachDefault(dockerCli.Err(), dockerCli.Client().ClientVersion(), flags, "updated")
return nil
}
@ -269,7 +270,7 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
}
}
cspec := spec.TaskTemplate.ContainerSpec
cspec := &spec.TaskTemplate.ContainerSpec
task := &spec.TaskTemplate
taskResources := func() *swarm.ResourceRequirements {

View File

@ -19,12 +19,8 @@ func TestUpdateServiceArgs(t *testing.T) {
flags := newUpdateCommand(nil).Flags()
flags.Set("args", "the \"new args\"")
spec := &swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{},
},
}
cspec := spec.TaskTemplate.ContainerSpec
spec := &swarm.ServiceSpec{}
cspec := &spec.TaskTemplate.ContainerSpec
cspec.Args = []string{"old", "args"}
updateService(nil, nil, flags, spec)
@ -456,12 +452,8 @@ func TestUpdateSecretUpdateInPlace(t *testing.T) {
}
func TestUpdateReadOnly(t *testing.T) {
spec := &swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{},
},
}
cspec := spec.TaskTemplate.ContainerSpec
spec := &swarm.ServiceSpec{}
cspec := &spec.TaskTemplate.ContainerSpec
// Update with --read-only=true, changed to true
flags := newUpdateCommand(nil).Flags()
@ -482,12 +474,8 @@ func TestUpdateReadOnly(t *testing.T) {
}
func TestUpdateStopSignal(t *testing.T) {
spec := &swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{},
},
}
cspec := spec.TaskTemplate.ContainerSpec
spec := &swarm.ServiceSpec{}
cspec := &spec.TaskTemplate.ContainerSpec
// Update with --stop-signal=SIGUSR1
flags := newUpdateCommand(nil).Flags()

View File

@ -104,12 +104,13 @@ func checkDaemonIsSwarmManager(ctx context.Context, dockerCli command.Cli) error
}
// pruneServices removes services that are no longer referenced in the source
func pruneServices(ctx context.Context, dockerCli command.Cli, namespace convert.Namespace, services map[string]struct{}) {
func pruneServices(ctx context.Context, dockerCli command.Cli, namespace convert.Namespace, services map[string]struct{}) bool {
client := dockerCli.Client()
oldServices, err := getServices(ctx, client, namespace.Name())
if err != nil {
fmt.Fprintf(dockerCli.Err(), "Failed to list services: %s", err)
return true
}
pruneServices := []swarm.Service{}
@ -118,5 +119,5 @@ func pruneServices(ctx context.Context, dockerCli command.Cli, namespace convert
pruneServices = append(pruneServices, service)
}
}
removeServices(ctx, dockerCli, pruneServices)
return removeServices(ctx, dockerCli, pruneServices)
}

View File

@ -64,7 +64,7 @@ func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions
Labels: convert.AddStackLabel(namespace, service.Labels),
},
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{
ContainerSpec: swarm.ContainerSpec{
Image: service.Image,
Command: service.Command,
Args: service.Args,

View File

@ -45,7 +45,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
Labels: map[string]string{"com.docker.stack.image": "foobar:1.2.3"},
},
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{
ContainerSpec: swarm.ContainerSpec{
Image: "foobar:1.2.3@sha256:deadbeef",
},
},
@ -86,7 +86,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
spec := map[string]swarm.ServiceSpec{
"myservice": {
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{
ContainerSpec: swarm.ContainerSpec{
Image: testcase.image,
},
},

View File

@ -59,7 +59,6 @@ func TestStackPsEmptyStack(t *testing.T) {
})
cmd := newPsCommand(fakeCli)
cmd.SetArgs([]string{"foo"})
cmd.SetOutput(ioutil.Discard)
assert.Error(t, cmd.Execute())
assert.EqualError(t, cmd.Execute(), "nothing found in stack: foo")

View File

@ -103,7 +103,7 @@ func removeServices(
var hasError bool
sort.Slice(services, sortServiceByName(services))
for _, service := range services {
fmt.Fprintf(dockerCli.Out(), "Removing service %s\n", service.Spec.Name)
fmt.Fprintf(dockerCli.Err(), "Removing service %s\n", service.Spec.Name)
if err := dockerCli.Client().ServiceRemove(ctx, service.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove service %s: %s", service.ID, err)
@ -119,7 +119,7 @@ func removeNetworks(
) bool {
var hasError bool
for _, network := range networks {
fmt.Fprintf(dockerCli.Out(), "Removing network %s\n", network.Name)
fmt.Fprintf(dockerCli.Err(), "Removing network %s\n", network.Name)
if err := dockerCli.Client().NetworkRemove(ctx, network.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove network %s: %s", network.ID, err)
@ -135,7 +135,7 @@ func removeSecrets(
) bool {
var hasError bool
for _, secret := range secrets {
fmt.Fprintf(dockerCli.Out(), "Removing secret %s\n", secret.Spec.Name)
fmt.Fprintf(dockerCli.Err(), "Removing secret %s\n", secret.Spec.Name)
if err := dockerCli.Client().SecretRemove(ctx, secret.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove secret %s: %s", secret.ID, err)
@ -151,7 +151,7 @@ func removeConfigs(
) bool {
var hasError bool
for _, config := range configs {
fmt.Fprintf(dockerCli.Out(), "Removing config %s\n", config.Spec.Name)
fmt.Fprintf(dockerCli.Err(), "Removing config %s\n", config.Spec.Name)
if err := dockerCli.Client().ConfigRemove(ctx, config.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove config %s: %s", config.ID, err)

View File

@ -101,13 +101,7 @@ func TestRemoveStackSkipEmpty(t *testing.T) {
cmd.SetArgs([]string{"foo", "bar"})
assert.NoError(t, cmd.Execute())
expectedList := []string{"Removing service bar_service1",
"Removing service bar_service2",
"Removing secret bar_secret1",
"Removing config bar_config1",
"Removing network bar_network1\n",
}
assert.Equal(t, strings.Join(expectedList, "\n"), fakeCli.OutBuffer().String())
assert.Equal(t, "", fakeCli.OutBuffer().String())
assert.Contains(t, fakeCli.ErrBuffer().String(), "Nothing found in stack: foo\n")
assert.Equal(t, allServiceIDs, fakeClient.removedServices)
assert.Equal(t, allNetworkIDs, fakeClient.removedNetworks)

View File

@ -15,20 +15,24 @@ import (
"golang.org/x/net/context"
)
type unlockOptions struct{}
func newUnlockCommand(dockerCli command.Cli) *cobra.Command {
opts := unlockOptions{}
cmd := &cobra.Command{
Use: "unlock",
Short: "Unlock swarm",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runUnlock(dockerCli)
return runUnlock(dockerCli, opts)
},
}
return cmd
}
func runUnlock(dockerCli command.Cli) error {
func runUnlock(dockerCli command.Cli, opts unlockOptions) error {
client := dockerCli.Client()
ctx := context.Background()

View File

@ -15,6 +15,7 @@ import (
"github.com/docker/cli/templates"
"github.com/docker/docker/api/types"
eventtypes "github.com/docker/docker/api/types/events"
"github.com/docker/docker/pkg/jsonlog"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
@ -103,18 +104,14 @@ func makeTemplate(format string) (*template.Template, error) {
return tmpl, tmpl.Execute(ioutil.Discard, &eventtypes.Message{})
}
// rfc3339NanoFixed is similar to time.RFC3339Nano, except it pads nanoseconds
// zeros to maintain a fixed number of characters
const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00"
// prettyPrintEvent prints all types of event information.
// Each output includes the event type, actor id, name and action.
// Actor attributes are printed at the end if the actor has any.
func prettyPrintEvent(out io.Writer, event eventtypes.Message) error {
if event.TimeNano != 0 {
fmt.Fprintf(out, "%s ", time.Unix(0, event.TimeNano).Format(rfc3339NanoFixed))
fmt.Fprintf(out, "%s ", time.Unix(0, event.TimeNano).Format(jsonlog.RFC3339NanoFixed))
} else if event.Time != 0 {
fmt.Fprintf(out, "%s ", time.Unix(event.Time, 0).Format(rfc3339NanoFixed))
fmt.Fprintf(out, "%s ", time.Unix(event.Time, 0).Format(jsonlog.RFC3339NanoFixed))
}
fmt.Fprintf(out, "%s %s %s", event.Type, event.Action, event.Actor.ID)

View File

@ -55,50 +55,109 @@ func runInfo(dockerCli *command.DockerCli, opts *infoOptions) error {
// nolint: gocyclo
func prettyPrintInfo(dockerCli command.Cli, info types.Info) error {
fmt.Fprintln(dockerCli.Out(), "Containers:", info.Containers)
fmt.Fprintln(dockerCli.Out(), " Running:", info.ContainersRunning)
fmt.Fprintln(dockerCli.Out(), " Paused:", info.ContainersPaused)
fmt.Fprintln(dockerCli.Out(), " Stopped:", info.ContainersStopped)
fmt.Fprintln(dockerCli.Out(), "Images:", info.Images)
fprintlnNonEmpty(dockerCli.Out(), "Server Version:", info.ServerVersion)
fprintlnNonEmpty(dockerCli.Out(), "Storage Driver:", info.Driver)
fmt.Fprintf(dockerCli.Out(), "Containers: %d\n", info.Containers)
fmt.Fprintf(dockerCli.Out(), " Running: %d\n", info.ContainersRunning)
fmt.Fprintf(dockerCli.Out(), " Paused: %d\n", info.ContainersPaused)
fmt.Fprintf(dockerCli.Out(), " Stopped: %d\n", info.ContainersStopped)
fmt.Fprintf(dockerCli.Out(), "Images: %d\n", info.Images)
fprintfIfNotEmpty(dockerCli.Out(), "Server Version: %s\n", info.ServerVersion)
fprintfIfNotEmpty(dockerCli.Out(), "Storage Driver: %s\n", info.Driver)
if info.DriverStatus != nil {
for _, pair := range info.DriverStatus {
fmt.Fprintf(dockerCli.Out(), " %s: %s\n", pair[0], pair[1])
}
}
if info.SystemStatus != nil {
for _, pair := range info.SystemStatus {
fmt.Fprintf(dockerCli.Out(), "%s: %s\n", pair[0], pair[1])
}
}
fprintlnNonEmpty(dockerCli.Out(), "Logging Driver:", info.LoggingDriver)
fprintlnNonEmpty(dockerCli.Out(), "Cgroup Driver:", info.CgroupDriver)
fprintfIfNotEmpty(dockerCli.Out(), "Logging Driver: %s\n", info.LoggingDriver)
fprintfIfNotEmpty(dockerCli.Out(), "Cgroup Driver: %s\n", info.CgroupDriver)
fmt.Fprintln(dockerCli.Out(), "Plugins:")
fmt.Fprintln(dockerCli.Out(), " Volume:", strings.Join(info.Plugins.Volume, " "))
fmt.Fprintln(dockerCli.Out(), " Network:", strings.Join(info.Plugins.Network, " "))
fmt.Fprintf(dockerCli.Out(), "Plugins:\n")
fmt.Fprintf(dockerCli.Out(), " Volume:")
fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Volume, " "))
fmt.Fprintf(dockerCli.Out(), "\n")
fmt.Fprintf(dockerCli.Out(), " Network:")
fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Network, " "))
fmt.Fprintf(dockerCli.Out(), "\n")
if len(info.Plugins.Authorization) != 0 {
fmt.Fprintln(dockerCli.Out(), " Authorization:", strings.Join(info.Plugins.Authorization, " "))
fmt.Fprintf(dockerCli.Out(), " Authorization:")
fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Authorization, " "))
fmt.Fprintf(dockerCli.Out(), "\n")
}
fmt.Fprintln(dockerCli.Out(), " Log:", strings.Join(info.Plugins.Log, " "))
fmt.Fprintf(dockerCli.Out(), " Log:")
fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Log, " "))
fmt.Fprintf(dockerCli.Out(), "\n")
fmt.Fprintln(dockerCli.Out(), "Swarm:", info.Swarm.LocalNodeState)
printSwarmInfo(dockerCli, info)
fmt.Fprintf(dockerCli.Out(), "Swarm: %v\n", info.Swarm.LocalNodeState)
if info.Swarm.LocalNodeState != swarm.LocalNodeStateInactive && info.Swarm.LocalNodeState != swarm.LocalNodeStateLocked {
fmt.Fprintf(dockerCli.Out(), " NodeID: %s\n", info.Swarm.NodeID)
if info.Swarm.Error != "" {
fmt.Fprintf(dockerCli.Out(), " Error: %v\n", info.Swarm.Error)
}
fmt.Fprintf(dockerCli.Out(), " Is Manager: %v\n", info.Swarm.ControlAvailable)
if info.Swarm.Cluster != nil && info.Swarm.ControlAvailable && info.Swarm.Error == "" && info.Swarm.LocalNodeState != swarm.LocalNodeStateError {
fmt.Fprintf(dockerCli.Out(), " ClusterID: %s\n", info.Swarm.Cluster.ID)
fmt.Fprintf(dockerCli.Out(), " Managers: %d\n", info.Swarm.Managers)
fmt.Fprintf(dockerCli.Out(), " Nodes: %d\n", info.Swarm.Nodes)
fmt.Fprintf(dockerCli.Out(), " Orchestration:\n")
taskHistoryRetentionLimit := int64(0)
if info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit != nil {
taskHistoryRetentionLimit = *info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit
}
fmt.Fprintf(dockerCli.Out(), " Task History Retention Limit: %d\n", taskHistoryRetentionLimit)
fmt.Fprintf(dockerCli.Out(), " Raft:\n")
fmt.Fprintf(dockerCli.Out(), " Snapshot Interval: %d\n", info.Swarm.Cluster.Spec.Raft.SnapshotInterval)
if info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots != nil {
fmt.Fprintf(dockerCli.Out(), " Number of Old Snapshots to Retain: %d\n", *info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots)
}
fmt.Fprintf(dockerCli.Out(), " Heartbeat Tick: %d\n", info.Swarm.Cluster.Spec.Raft.HeartbeatTick)
fmt.Fprintf(dockerCli.Out(), " Election Tick: %d\n", info.Swarm.Cluster.Spec.Raft.ElectionTick)
fmt.Fprintf(dockerCli.Out(), " Dispatcher:\n")
fmt.Fprintf(dockerCli.Out(), " Heartbeat Period: %s\n", units.HumanDuration(info.Swarm.Cluster.Spec.Dispatcher.HeartbeatPeriod))
fmt.Fprintf(dockerCli.Out(), " CA Configuration:\n")
fmt.Fprintf(dockerCli.Out(), " Expiry Duration: %s\n", units.HumanDuration(info.Swarm.Cluster.Spec.CAConfig.NodeCertExpiry))
fmt.Fprintf(dockerCli.Out(), " Force Rotate: %d\n", info.Swarm.Cluster.Spec.CAConfig.ForceRotate)
fprintfIfNotEmpty(dockerCli.Out(), " Signing CA Certificate: \n%s\n\n", strings.TrimSpace(info.Swarm.Cluster.Spec.CAConfig.SigningCACert))
if len(info.Swarm.Cluster.Spec.CAConfig.ExternalCAs) > 0 {
fmt.Fprintf(dockerCli.Out(), " External CAs:\n")
for _, entry := range info.Swarm.Cluster.Spec.CAConfig.ExternalCAs {
fmt.Fprintf(dockerCli.Out(), " %s: %s\n", entry.Protocol, entry.URL)
}
}
fmt.Fprintf(dockerCli.Out(), " Autolock Managers: %v\n", info.Swarm.Cluster.Spec.EncryptionConfig.AutoLockManagers)
fmt.Fprintf(dockerCli.Out(), " Root Rotation In Progress: %v\n", info.Swarm.Cluster.RootRotationInProgress)
}
fmt.Fprintf(dockerCli.Out(), " Node Address: %s\n", info.Swarm.NodeAddr)
managers := []string{}
for _, entry := range info.Swarm.RemoteManagers {
managers = append(managers, entry.Addr)
}
if len(managers) > 0 {
sort.Strings(managers)
fmt.Fprintf(dockerCli.Out(), " Manager Addresses:\n")
for _, entry := range managers {
fmt.Fprintf(dockerCli.Out(), " %s\n", entry)
}
}
}
if len(info.Runtimes) > 0 {
fmt.Fprint(dockerCli.Out(), "Runtimes:")
fmt.Fprintf(dockerCli.Out(), "Runtimes:")
for name := range info.Runtimes {
fmt.Fprintf(dockerCli.Out(), " %s", name)
}
fmt.Fprint(dockerCli.Out(), "\n")
fmt.Fprintln(dockerCli.Out(), "Default Runtime:", info.DefaultRuntime)
fmt.Fprintf(dockerCli.Out(), "Default Runtime: %s\n", info.DefaultRuntime)
}
if info.OSType == "linux" {
fmt.Fprintln(dockerCli.Out(), "Init Binary:", info.InitBinary)
fmt.Fprintf(dockerCli.Out(), "Init Binary: %v\n", info.InitBinary)
for _, ci := range []struct {
Name string
@ -112,23 +171,23 @@ func prettyPrintInfo(dockerCli command.Cli, info types.Info) error {
if ci.Commit.ID != ci.Commit.Expected {
fmt.Fprintf(dockerCli.Out(), " (expected: %s)", ci.Commit.Expected)
}
fmt.Fprint(dockerCli.Out(), "\n")
fmt.Fprintf(dockerCli.Out(), "\n")
}
if len(info.SecurityOptions) != 0 {
kvs, err := types.DecodeSecurityOptions(info.SecurityOptions)
if err != nil {
return err
}
fmt.Fprintln(dockerCli.Out(), "Security Options:")
fmt.Fprintf(dockerCli.Out(), "Security Options:\n")
for _, so := range kvs {
fmt.Fprintln(dockerCli.Out(), " "+so.Name)
fmt.Fprintf(dockerCli.Out(), " %s\n", so.Name)
for _, o := range so.Options {
switch o.Key {
case "profile":
if o.Value != "default" {
fmt.Fprintln(dockerCli.Err(), " WARNING: You're not using the default seccomp profile")
fmt.Fprintf(dockerCli.Err(), " WARNING: You're not using the default seccomp profile\n")
}
fmt.Fprintln(dockerCli.Out(), " Profile:", o.Value)
fmt.Fprintf(dockerCli.Out(), " Profile: %s\n", o.Value)
}
}
}
@ -137,44 +196,44 @@ func prettyPrintInfo(dockerCli command.Cli, info types.Info) error {
// Isolation only has meaning on a Windows daemon.
if info.OSType == "windows" {
fmt.Fprintln(dockerCli.Out(), "Default Isolation:", info.Isolation)
fmt.Fprintf(dockerCli.Out(), "Default Isolation: %v\n", info.Isolation)
}
fprintlnNonEmpty(dockerCli.Out(), "Kernel Version:", info.KernelVersion)
fprintlnNonEmpty(dockerCli.Out(), "Operating System:", info.OperatingSystem)
fprintlnNonEmpty(dockerCli.Out(), "OSType:", info.OSType)
fprintlnNonEmpty(dockerCli.Out(), "Architecture:", info.Architecture)
fmt.Fprintln(dockerCli.Out(), "CPUs:", info.NCPU)
fmt.Fprintln(dockerCli.Out(), "Total Memory:", units.BytesSize(float64(info.MemTotal)))
fprintlnNonEmpty(dockerCli.Out(), "Name:", info.Name)
fprintlnNonEmpty(dockerCli.Out(), "ID:", info.ID)
fmt.Fprintln(dockerCli.Out(), "Docker Root Dir:", info.DockerRootDir)
fmt.Fprintln(dockerCli.Out(), "Debug Mode (client):", debug.IsEnabled())
fmt.Fprintln(dockerCli.Out(), "Debug Mode (server):", info.Debug)
fprintfIfNotEmpty(dockerCli.Out(), "Kernel Version: %s\n", info.KernelVersion)
fprintfIfNotEmpty(dockerCli.Out(), "Operating System: %s\n", info.OperatingSystem)
fprintfIfNotEmpty(dockerCli.Out(), "OSType: %s\n", info.OSType)
fprintfIfNotEmpty(dockerCli.Out(), "Architecture: %s\n", info.Architecture)
fmt.Fprintf(dockerCli.Out(), "CPUs: %d\n", info.NCPU)
fmt.Fprintf(dockerCli.Out(), "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal)))
fprintfIfNotEmpty(dockerCli.Out(), "Name: %s\n", info.Name)
fprintfIfNotEmpty(dockerCli.Out(), "ID: %s\n", info.ID)
fmt.Fprintf(dockerCli.Out(), "Docker Root Dir: %s\n", info.DockerRootDir)
fmt.Fprintf(dockerCli.Out(), "Debug Mode (client): %v\n", debug.IsEnabled())
fmt.Fprintf(dockerCli.Out(), "Debug Mode (server): %v\n", info.Debug)
if info.Debug {
fmt.Fprintln(dockerCli.Out(), " File Descriptors:", info.NFd)
fmt.Fprintln(dockerCli.Out(), " Goroutines:", info.NGoroutines)
fmt.Fprintln(dockerCli.Out(), " System Time:", info.SystemTime)
fmt.Fprintln(dockerCli.Out(), " EventsListeners:", info.NEventsListener)
fmt.Fprintf(dockerCli.Out(), " File Descriptors: %d\n", info.NFd)
fmt.Fprintf(dockerCli.Out(), " Goroutines: %d\n", info.NGoroutines)
fmt.Fprintf(dockerCli.Out(), " System Time: %s\n", info.SystemTime)
fmt.Fprintf(dockerCli.Out(), " EventsListeners: %d\n", info.NEventsListener)
}
fprintlnNonEmpty(dockerCli.Out(), "HTTP Proxy:", info.HTTPProxy)
fprintlnNonEmpty(dockerCli.Out(), "HTTPS Proxy:", info.HTTPSProxy)
fprintlnNonEmpty(dockerCli.Out(), "No Proxy:", info.NoProxy)
fprintfIfNotEmpty(dockerCli.Out(), "Http Proxy: %s\n", info.HTTPProxy)
fprintfIfNotEmpty(dockerCli.Out(), "Https Proxy: %s\n", info.HTTPSProxy)
fprintfIfNotEmpty(dockerCli.Out(), "No Proxy: %s\n", info.NoProxy)
if info.IndexServerAddress != "" {
u := dockerCli.ConfigFile().AuthConfigs[info.IndexServerAddress].Username
if len(u) > 0 {
fmt.Fprintln(dockerCli.Out(), "Username:", u)
fmt.Fprintf(dockerCli.Out(), "Username: %v\n", u)
}
fmt.Fprintln(dockerCli.Out(), "Registry:", info.IndexServerAddress)
fmt.Fprintf(dockerCli.Out(), "Registry: %v\n", info.IndexServerAddress)
}
if info.Labels != nil {
fmt.Fprintln(dockerCli.Out(), "Labels:")
for _, lbl := range info.Labels {
fmt.Fprintln(dockerCli.Out(), " "+lbl)
for _, attribute := range info.Labels {
fmt.Fprintf(dockerCli.Out(), " %s\n", attribute)
}
// TODO: Engine labels with duplicate keys has been deprecated in 1.13 and will be error out
// after 3 release cycles (17.12). For now, a WARNING will be generated. The following will
@ -193,15 +252,20 @@ func prettyPrintInfo(dockerCli command.Cli, info types.Info) error {
}
}
fmt.Fprintln(dockerCli.Out(), "Experimental:", info.ExperimentalBuild)
fprintlnNonEmpty(dockerCli.Out(), "Cluster Store:", info.ClusterStore)
fprintlnNonEmpty(dockerCli.Out(), "Cluster Advertise:", info.ClusterAdvertise)
fmt.Fprintf(dockerCli.Out(), "Experimental: %v\n", info.ExperimentalBuild)
if info.ClusterStore != "" {
fmt.Fprintf(dockerCli.Out(), "Cluster Store: %s\n", info.ClusterStore)
}
if info.ClusterAdvertise != "" {
fmt.Fprintf(dockerCli.Out(), "Cluster Advertise: %s\n", info.ClusterAdvertise)
}
if info.RegistryConfig != nil && (len(info.RegistryConfig.InsecureRegistryCIDRs) > 0 || len(info.RegistryConfig.IndexConfigs) > 0) {
fmt.Fprintln(dockerCli.Out(), "Insecure Registries:")
for _, registry := range info.RegistryConfig.IndexConfigs {
if !registry.Secure {
fmt.Fprintln(dockerCli.Out(), " "+registry.Name)
fmt.Fprintf(dockerCli.Out(), " %s\n", registry.Name)
}
}
@ -214,12 +278,11 @@ func prettyPrintInfo(dockerCli command.Cli, info types.Info) error {
if info.RegistryConfig != nil && len(info.RegistryConfig.Mirrors) > 0 {
fmt.Fprintln(dockerCli.Out(), "Registry Mirrors:")
for _, mirror := range info.RegistryConfig.Mirrors {
fmt.Fprintln(dockerCli.Out(), " "+mirror)
fmt.Fprintf(dockerCli.Out(), " %s\n", mirror)
}
}
fmt.Fprintln(dockerCli.Out(), "Live Restore Enabled:", info.LiveRestoreEnabled)
fmt.Fprint(dockerCli.Out(), "\n")
fmt.Fprintf(dockerCli.Out(), "Live Restore Enabled: %v\n\n", info.LiveRestoreEnabled)
// Only output these warnings if the server does not support these features
if info.OSType != "windows" {
@ -263,63 +326,6 @@ func prettyPrintInfo(dockerCli command.Cli, info types.Info) error {
return nil
}
func printSwarmInfo(dockerCli command.Cli, info types.Info) {
if info.Swarm.LocalNodeState == swarm.LocalNodeStateInactive || info.Swarm.LocalNodeState == swarm.LocalNodeStateLocked {
return
}
fmt.Fprintln(dockerCli.Out(), " NodeID:", info.Swarm.NodeID)
if info.Swarm.Error != "" {
fmt.Fprintln(dockerCli.Out(), " Error:", info.Swarm.Error)
}
fmt.Fprintln(dockerCli.Out(), " Is Manager:", info.Swarm.ControlAvailable)
if info.Swarm.Cluster != nil && info.Swarm.ControlAvailable && info.Swarm.Error == "" && info.Swarm.LocalNodeState != swarm.LocalNodeStateError {
fmt.Fprintln(dockerCli.Out(), " ClusterID:", info.Swarm.Cluster.ID)
fmt.Fprintln(dockerCli.Out(), " Managers:", info.Swarm.Managers)
fmt.Fprintln(dockerCli.Out(), " Nodes:", info.Swarm.Nodes)
fmt.Fprintln(dockerCli.Out(), " Orchestration:")
taskHistoryRetentionLimit := int64(0)
if info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit != nil {
taskHistoryRetentionLimit = *info.Swarm.Cluster.Spec.Orchestration.TaskHistoryRetentionLimit
}
fmt.Fprintln(dockerCli.Out(), " Task History Retention Limit:", taskHistoryRetentionLimit)
fmt.Fprintln(dockerCli.Out(), " Raft:")
fmt.Fprintln(dockerCli.Out(), " Snapshot Interval:", info.Swarm.Cluster.Spec.Raft.SnapshotInterval)
if info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots != nil {
fmt.Fprintf(dockerCli.Out(), " Number of Old Snapshots to Retain: %d\n", *info.Swarm.Cluster.Spec.Raft.KeepOldSnapshots)
}
fmt.Fprintln(dockerCli.Out(), " Heartbeat Tick:", info.Swarm.Cluster.Spec.Raft.HeartbeatTick)
fmt.Fprintln(dockerCli.Out(), " Election Tick:", info.Swarm.Cluster.Spec.Raft.ElectionTick)
fmt.Fprintln(dockerCli.Out(), " Dispatcher:")
fmt.Fprintln(dockerCli.Out(), " Heartbeat Period:", units.HumanDuration(info.Swarm.Cluster.Spec.Dispatcher.HeartbeatPeriod))
fmt.Fprintln(dockerCli.Out(), " CA Configuration:")
fmt.Fprintln(dockerCli.Out(), " Expiry Duration:", units.HumanDuration(info.Swarm.Cluster.Spec.CAConfig.NodeCertExpiry))
fmt.Fprintln(dockerCli.Out(), " Force Rotate:", info.Swarm.Cluster.Spec.CAConfig.ForceRotate)
if caCert := strings.TrimSpace(info.Swarm.Cluster.Spec.CAConfig.SigningCACert); caCert != "" {
fmt.Fprintf(dockerCli.Out(), " Signing CA Certificate: \n%s\n\n", caCert)
}
if len(info.Swarm.Cluster.Spec.CAConfig.ExternalCAs) > 0 {
fmt.Fprintln(dockerCli.Out(), " External CAs:")
for _, entry := range info.Swarm.Cluster.Spec.CAConfig.ExternalCAs {
fmt.Fprintf(dockerCli.Out(), " %s: %s\n", entry.Protocol, entry.URL)
}
}
fmt.Fprintln(dockerCli.Out(), " Autolock Managers:", info.Swarm.Cluster.Spec.EncryptionConfig.AutoLockManagers)
fmt.Fprintln(dockerCli.Out(), " Root Rotation In Progress:", info.Swarm.Cluster.RootRotationInProgress)
}
fmt.Fprintln(dockerCli.Out(), " Node Address:", info.Swarm.NodeAddr)
if len(info.Swarm.RemoteManagers) > 0 {
managers := []string{}
for _, entry := range info.Swarm.RemoteManagers {
managers = append(managers, entry.Addr)
}
sort.Strings(managers)
fmt.Fprintln(dockerCli.Out(), " Manager Addresses:")
for _, entry := range managers {
fmt.Fprintf(dockerCli.Out(), " %s\n", entry)
}
}
}
func printStorageDriverWarnings(dockerCli command.Cli, info types.Info) {
if info.DriverStatus == nil {
return
@ -368,8 +374,9 @@ func formatInfo(dockerCli *command.DockerCli, info types.Info, format string) er
return err
}
func fprintlnNonEmpty(w io.Writer, label, value string) {
func fprintfIfNotEmpty(w io.Writer, format, value string) (int, error) {
if value != "" {
fmt.Fprintln(w, label, value)
return fmt.Fprintf(w, format, value)
}
return 0, nil
}

View File

@ -1,413 +0,0 @@
package trust
import (
"github.com/docker/cli/cli/trust"
"github.com/docker/notary/client"
"github.com/docker/notary/client/changelist"
"github.com/docker/notary/cryptoservice"
"github.com/docker/notary/passphrase"
"github.com/docker/notary/storage"
"github.com/docker/notary/trustmanager"
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
)
// Sample mock CLI interfaces
func getOfflineNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return OfflineNotaryRepository{}, nil
}
// OfflineNotaryRepository is a mock Notary repository that is offline
type OfflineNotaryRepository struct{}
func (o OfflineNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error {
return storage.ErrOffline{}
}
func (o OfflineNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error {
return storage.ErrOffline{}
}
func (o OfflineNotaryRepository) Publish() error {
return storage.ErrOffline{}
}
func (o OfflineNotaryRepository) AddTarget(target *client.Target, roles ...data.RoleName) error {
return nil
}
func (o OfflineNotaryRepository) RemoveTarget(targetName string, roles ...data.RoleName) error {
return nil
}
func (o OfflineNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
return nil, storage.ErrOffline{}
}
func (o OfflineNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
return nil, storage.ErrOffline{}
}
func (o OfflineNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
return nil, storage.ErrOffline{}
}
func (o OfflineNotaryRepository) GetChangelist() (changelist.Changelist, error) {
return changelist.NewMemChangelist(), nil
}
func (o OfflineNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
return nil, storage.ErrOffline{}
}
func (o OfflineNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return nil, storage.ErrOffline{}
}
func (o OfflineNotaryRepository) AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error {
return nil
}
func (o OfflineNotaryRepository) AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error {
return nil
}
func (o OfflineNotaryRepository) AddDelegationPaths(name data.RoleName, paths []string) error {
return nil
}
func (o OfflineNotaryRepository) RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error {
return nil
}
func (o OfflineNotaryRepository) RemoveDelegationRole(name data.RoleName) error {
return nil
}
func (o OfflineNotaryRepository) RemoveDelegationPaths(name data.RoleName, paths []string) error {
return nil
}
func (o OfflineNotaryRepository) RemoveDelegationKeys(name data.RoleName, keyIDs []string) error {
return nil
}
func (o OfflineNotaryRepository) ClearDelegationPaths(name data.RoleName) error {
return nil
}
func (o OfflineNotaryRepository) Witness(roles ...data.RoleName) ([]data.RoleName, error) {
return nil, nil
}
func (o OfflineNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error {
return storage.ErrOffline{}
}
func (o OfflineNotaryRepository) GetCryptoService() signed.CryptoService {
return nil
}
func (o OfflineNotaryRepository) SetLegacyVersions(version int) {}
func (o OfflineNotaryRepository) GetGUN() data.GUN {
return data.GUN("gun")
}
func getUninitializedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return UninitializedNotaryRepository{}, nil
}
// UninitializedNotaryRepository is a mock Notary repository that is uninintialized
// it builds on top of the OfflineNotaryRepository, instead returning ErrRepositoryNotExist
// for any online operation
type UninitializedNotaryRepository struct {
OfflineNotaryRepository
}
func (u UninitializedNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error {
return client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error {
return client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) Publish() error {
return client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
return nil, client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
return nil, client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
return nil, client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
return nil, client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return nil, client.ErrRepositoryNotExist{}
}
func (u UninitializedNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error {
return client.ErrRepositoryNotExist{}
}
func getEmptyTargetsNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return EmptyTargetsNotaryRepository{}, nil
}
// EmptyTargetsNotaryRepository is a mock Notary repository that is initialized
// but does not have any signed targets
type EmptyTargetsNotaryRepository struct {
OfflineNotaryRepository
}
func (e EmptyTargetsNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error {
return nil
}
func (e EmptyTargetsNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error {
return nil
}
func (e EmptyTargetsNotaryRepository) Publish() error {
return nil
}
func (e EmptyTargetsNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
return []*client.TargetWithRole{}, nil
}
func (e EmptyTargetsNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
return nil, client.ErrNoSuchTarget(name)
}
func (e EmptyTargetsNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
return nil, client.ErrNoSuchTarget(name)
}
func (e EmptyTargetsNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
return []client.RoleWithSignatures{}, nil
}
func (e EmptyTargetsNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return []data.Role{}, nil
}
func (e EmptyTargetsNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error {
return nil
}
func getLoadedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return LoadedNotaryRepository{}, nil
}
// LoadedNotaryRepository is a mock Notary repository that is loaded with targets, delegations, and keys
type LoadedNotaryRepository struct {
EmptyTargetsNotaryRepository
statefulCryptoService signed.CryptoService
}
// LoadedNotaryRepository has three delegations:
// - targets/releases: includes keys A and B
// - targets/alice: includes key A
// - targets/bob: includes key B
var loadedReleasesRole = data.DelegationRole{
BaseRole: data.BaseRole{
Name: "targets/releases",
Keys: map[string]data.PublicKey{"A": nil, "B": nil},
Threshold: 1,
},
}
var loadedAliceRole = data.DelegationRole{
BaseRole: data.BaseRole{
Name: "targets/alice",
Keys: map[string]data.PublicKey{"A": nil},
Threshold: 1,
},
}
var loadedBobRole = data.DelegationRole{
BaseRole: data.BaseRole{
Name: "targets/bob",
Keys: map[string]data.PublicKey{"B": nil},
Threshold: 1,
},
}
var loadedDelegationRoles = []data.Role{
{
Name: loadedReleasesRole.Name,
RootRole: data.RootRole{
KeyIDs: []string{"A", "B"},
Threshold: 1,
},
},
{
Name: loadedAliceRole.Name,
RootRole: data.RootRole{
KeyIDs: []string{"A"},
Threshold: 1,
},
},
{
Name: loadedBobRole.Name,
RootRole: data.RootRole{
KeyIDs: []string{"B"},
Threshold: 1,
},
},
}
var loadedTargetsRole = data.DelegationRole{
BaseRole: data.BaseRole{
Name: data.CanonicalTargetsRole,
Keys: map[string]data.PublicKey{"C": nil},
Threshold: 1,
},
}
// LoadedNotaryRepository has three targets:
// - red: signed by targets/releases, targets/alice, targets/bob
// - blue: signed by targets/releases, targets/alice
// - green: signed by targets/releases
var loadedRedTarget = client.Target{
Name: "red",
Hashes: data.Hashes{"sha256": []byte("red-digest")},
}
var loadedBlueTarget = client.Target{
Name: "blue",
Hashes: data.Hashes{"sha256": []byte("blue-digest")},
}
var loadedGreenTarget = client.Target{
Name: "green",
Hashes: data.Hashes{"sha256": []byte("green-digest")},
}
var loadedTargets = []client.TargetSignedStruct{
// red is signed by all three delegations
{Target: loadedRedTarget, Role: loadedReleasesRole},
{Target: loadedRedTarget, Role: loadedAliceRole},
{Target: loadedRedTarget, Role: loadedBobRole},
// blue is signed by targets/releases, targets/alice
{Target: loadedBlueTarget, Role: loadedReleasesRole},
{Target: loadedBlueTarget, Role: loadedAliceRole},
// green is signed by targets/releases
{Target: loadedGreenTarget, Role: loadedReleasesRole},
}
func (l LoadedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) {
rootRole := data.Role{
RootRole: data.RootRole{
KeyIDs: []string{"rootID"},
Threshold: 1,
},
Name: data.CanonicalRootRole,
}
targetsRole := data.Role{
RootRole: data.RootRole{
KeyIDs: []string{"targetsID"},
Threshold: 1,
},
Name: data.CanonicalTargetsRole,
}
return []client.RoleWithSignatures{{Role: rootRole}, {Role: targetsRole}}, nil
}
func (l LoadedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
filteredTargets := []*client.TargetWithRole{}
for _, tgt := range loadedTargets {
if len(roles) == 0 || (len(roles) > 0 && roles[0] == tgt.Role.Name) {
filteredTargets = append(filteredTargets, &client.TargetWithRole{Target: tgt.Target, Role: tgt.Role.Name})
}
}
return filteredTargets, nil
}
func (l LoadedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
for _, tgt := range loadedTargets {
if name == tgt.Target.Name {
if len(roles) == 0 || (len(roles) > 0 && roles[0] == tgt.Role.Name) {
return &client.TargetWithRole{Target: tgt.Target, Role: tgt.Role.Name}, nil
}
}
}
return nil, client.ErrNoSuchTarget(name)
}
func (l LoadedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
if name == "" {
return loadedTargets, nil
}
filteredTargets := []client.TargetSignedStruct{}
for _, tgt := range loadedTargets {
if name == tgt.Target.Name {
filteredTargets = append(filteredTargets, tgt)
}
}
if len(filteredTargets) == 0 {
return nil, client.ErrNoSuchTarget(name)
}
return filteredTargets, nil
}
func (l LoadedNotaryRepository) GetGUN() data.GUN {
return data.GUN("signed-repo")
}
func (l LoadedNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return loadedDelegationRoles, nil
}
func (l LoadedNotaryRepository) GetCryptoService() signed.CryptoService {
if l.statefulCryptoService == nil {
// give it an in-memory cryptoservice with a root key and targets key
l.statefulCryptoService = cryptoservice.NewCryptoService(trustmanager.NewKeyMemoryStore(passphrase.ConstantRetriever("password")))
l.statefulCryptoService.AddKey(data.CanonicalRootRole, l.GetGUN(), nil)
l.statefulCryptoService.AddKey(data.CanonicalTargetsRole, l.GetGUN(), nil)
}
return l.statefulCryptoService
}
func getLoadedWithNoSignersNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) {
return LoadedWithNoSignersNotaryRepository{}, nil
}
// LoadedWithNoSignersNotaryRepository is a mock Notary repository that is loaded with targets but no delegations
// it only contains the green target
type LoadedWithNoSignersNotaryRepository struct {
LoadedNotaryRepository
}
func (l LoadedWithNoSignersNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) {
filteredTargets := []*client.TargetWithRole{}
for _, tgt := range loadedTargets {
if len(roles) == 0 || (len(roles) > 0 && roles[0] == tgt.Role.Name) {
filteredTargets = append(filteredTargets, &client.TargetWithRole{Target: tgt.Target, Role: tgt.Role.Name})
}
}
return filteredTargets, nil
}
func (l LoadedWithNoSignersNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) {
if name == "" || name == loadedGreenTarget.Name {
return &client.TargetWithRole{Target: loadedGreenTarget, Role: data.CanonicalTargetsRole}, nil
}
return nil, client.ErrNoSuchTarget(name)
}
func (l LoadedWithNoSignersNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) {
if name == "" || name == loadedGreenTarget.Name {
return []client.TargetSignedStruct{{Target: loadedGreenTarget, Role: loadedTargetsRole}}, nil
}
return nil, client.ErrNoSuchTarget(name)
}
func (l LoadedWithNoSignersNotaryRepository) GetDelegationRoles() ([]data.Role, error) {
return []data.Role{}, nil
}

View File

@ -1,23 +0,0 @@
package trust
import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
)
// NewTrustCommand returns a cobra command for `trust` subcommands
func NewTrustCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "trust",
Short: "Manage trust on Docker images (experimental)",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
}
cmd.AddCommand(
newViewCommand(dockerCli),
newRevokeCommand(dockerCli),
newSignCommand(dockerCli),
)
return cmd
}

Some files were not shown because too many files have changed in this diff Show More