Compare commits

..

5 Commits

Author SHA1 Message Date
7f8486a39a pass in git commit override when building deb
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit 3c70b56937c415c6bf3516aeaa5231c6d7d3145d)
2017-05-25 06:00:03 +00:00
5f615dd295 override ver and git commit when building all deb pkgs
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit 4397990e00863254d78c44fbe904a70272b5b340)
2017-05-25 05:59:47 +00:00
f3810787c8 pass in optional GITCOMMIT override
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit 446af3a9b7)
2017-05-24 23:03:09 +00:00
2bcfe6ffc2 bump VERSION files to 17.06.0-ce-rc1
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-05-24 02:24:43 +00:00
0b4548c769 allow version number to be set in builds
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit 3dfe334a7a)
2017-05-24 02:21:53 +00:00
1992 changed files with 71095 additions and 104007 deletions

View File

@ -5,68 +5,6 @@ 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.07.0-ce (2017-07-XX)
### API & Client
* Add support for proxy configuration in config.json [docker/cli#93](https://github.com/docker/cli/pull/93)
* Enable pprof/debug endpoints by default [moby/moby#32453](https://github.com/moby/moby/pull/32453)
* Passwords can now be passed using `STDIN` using the new `--password-stdin` flag on `docker login` [docker/cli#271](https://github.com/docker/cli/pull/271)
+ Add `--detach` to docker scale [docker/cli#243](https://github.com/docker/cli/pull/243)
* Prevent `docker logs --no-stream` from hanging due to non-existing containers [moby/moby#34004](https://github.com/moby/moby/pull/34004)
- Fix `docker stack ps` printing error to `stdout` instead of `stderr` [docker/cli#298](https://github.com/docker/cli/pull/298)
* Fix progress bar being stuck on `docker service create` if an error occurs during deploy [docker/cli#259](https://github.com/docker/cli/pull/259)
* Improve presentation of progress bars in interactive mode [docker/cli#260](https://github.com/docker/cli/pull/260) [docker/cli#237](https://github.com/docker/cli/pull/237)
* Print a warning if `docker login --password` is used, and recommend `--password-stdin` [docker/cli#270](https://github.com/docker/cli/pull/270)
* Make API version negotiation more robust [moby/moby#33827](https://github.com/moby/moby/pull/33827)
* Hide `--detach` when connected to daemons older than Docker 17.05 [docker/cli#219](https://github.com/docker/cli/pull/219)
+ Add `scope` filter in `GET /networks/(id or name)` [moby/moby#33630](https://github.com/moby/moby/pull/33630)
### Builder
* Implement long running interactive session and sending build context incrementally [moby/moby#32677](https://github.com/moby/moby/pull/32677) [docker/cli#231](https://github.com/docker/cli/pull/231) [moby/moby#33859](https://github.com/moby/moby/pull/33859)
* Warn on empty continuation lines [moby/moby#33719](https://github.com/moby/moby/pull/33719)
- Fix `.dockerignore` entries with a leading `/` not matching anything [moby/moby#32088](https://github.com/moby/moby/pull/32088)
### Logging
- Fix wrong filemode for rotate log files [moby/moby#33926](https://github.com/moby/moby/pull/33926)
- Fix stderr logging for journald and syslog [moby/moby#33832](https://github.com/moby/moby/pull/33832)
### Runtime
* Allow stopping of paused container [moby/moby#34027](https://github.com/moby/moby/pull/34027)
+ Add quota support for the overlay2 storage driver [moby/moby#32977](https://github.com/moby/moby/pull/32977)
* Remove container locks on `docker ps` [moby/moby#31273](https://github.com/moby/moby/pull/31273)
* Store container names in memdb [moby/moby#33886](https://github.com/moby/moby/pull/33886)
* Fix race condition between `docker exec` and `docker pause` [moby/moby#32881](https://github.com/moby/moby/pull/32881)
* Devicemapper: Rework logging and add `--storage-opt dm.libdm_log_level` [moby/moby#33845](https://github.com/moby/moby/pull/33845)
* Devicemapper: Prevent "device in use" errors if deferred removal is enabled, but not deferred deletion [moby/moby#33877](https://github.com/moby/moby/pull/33877)
* Devicemapper: Use KeepAlive to prevent tasks being garbage-collected while still in use [moby/moby#33376](https://github.com/moby/moby/pull/33376)
* Report inetermediate prune results if prune is cancelled [moby/moby#33979](https://github.com/moby/moby/pull/33979)
- Fix run `docker rename <container-id> new_name` concurrently resulting in the having multiple names [moby/moby#33940](https://github.com/moby/moby/pull/33940)
* Fix file-descriptor leak and error handling [moby/moby#33713](https://github.com/moby/moby/pull/33713)
- Fix SIGSEGV when running containers [docker/cli#303](https://github.com/docker/cli/pull/303)
* Prevent a goroutine leak when healthcheck gets stopped [moby/moby#33781](https://github.com/moby/moby/pull/33781)
* Image: Improve store locking [moby/moby#33755](https://github.com/moby/moby/pull/33755)
* Fix Btrfs quota groups not being removed when container is destroyed [moby/moby#29427](https://github.com/moby/moby/pull/29427)
* Libcontainerd: fix defunct containerd processes not being properly reaped [moby/moby#33419](https://github.com/moby/moby/pull/33419)
* Preparations for Linux Containers on Windows
* LCOW: Dedicated scratch space for service VM utilities [moby/moby#33809](https://github.com/moby/moby/pull/33809)
* LCOW: Support most operations excluding remote filesystem [moby/moby#33241](https://github.com/moby/moby/pull/33241) [moby/moby#33826](https://github.com/moby/moby/pull/33826)
* LCOW: Change directory from lcow to "Linux Containers" [moby/moby#33835](https://github.com/moby/moby/pull/33835)
* LCOW: pass command arguments without extra quoting [moby/moby#33815](https://github.com/moby/moby/pull/33815)
* LCOW: Updates necessary due to platform schema change [moby/moby#33785](https://github.com/moby/moby/pull/33785)
### Swarm Mode
* Add support for plugins on swarm [moby/moby#33575](https://github.com/moby/moby/pull/33575)
* Initial support for plugable secret backends [moby/moby#34157](https://github.com/moby/moby/pull/34157) [moby/moby#34123](https://github.com/moby/moby/pull/34123)
* Sort swarm stacks and nodes using natural sorting [docker/cli#315](https://github.com/docker/cli/pull/315)
* Make engine support cluster config event [moby/moby#34032](https://github.com/moby/moby/pull/34032)
* Only pass a join address when in the process of joining a cluster [moby/moby#33361](https://github.com/moby/moby/pull/33361)
* Fix error during service creation if a network with the same name exists both as "local" and "swarm" scoped network [docker/cli#184](https://github.com/docker/cli/pull/184)
## 17.06.0-ce (2017-06-07)
### Builder

View File

@ -4,4 +4,3 @@ Want to contribute on Docker CE? Awesome!
* Changes to the `engine` should be directed upstream to https://github.com/moby/moby
* Changes to the `cli` should be directed upstream to https://github.com/docker/cli
* Changes to the `packaging` should be directed upstream to https://github.com/docker/docker-ce-packaging

View File

@ -25,6 +25,3 @@ clean: ## clean the build artifacts
-$(MAKE) -C $(CLI_DIR) clean
-$(MAKE) -C $(ENGINE_DIR) clean
-$(MAKE) -C $(PACKAGING_DIR) clean
vendor: $(CLI_DIR)/build/docker
docker run --rm -it -v $(CLI_DIR):/go/src/github.com/docker/cli -v $(ENGINE_DIR):/go/src/github.com/docker/docker docker-cli-dev sh -c 'cd /go/src/github.com/docker/docker && git init && git add . && git -c user.name=user -c user.email=email@example.com commit -m first && cd /go/src/github.com/docker/cli && vndr; rm -rf /go/src/github.com/docker/docker/.git'

View File

@ -1,24 +1,12 @@
# Docker CE
This repository hosts open source components of Docker CE products. The
`master` branch serves to unify the upstream components on a regular
basis. Long-lived release branches host the code that goes into a product
version for the lifetime of the product.
This repository hosts open source components of Docker Community Edition
(CE) products. The `master` branch serves to unify the upstream components
on a regular basis. Long-lived release branches host the code that goes
into a product version for the lifetime of the product.
This repository is solely maintained by Docker, Inc.
## Issues
There are separate issue-tracking repos for the end user Docker CE
products specialized for a platform. Find your issue or file a new issue
for the platform you are using:
* https://github.com/docker/for-linux
* https://github.com/docker/for-mac
* https://github.com/docker/for-win
* https://github.com/docker/for-aws
* https://github.com/docker/for-azure
## Unifying upstream sources
The `master` branch is a combination of components adapted from

View File

@ -1 +1 @@
17.07.0-ce-rc2
17.06.0-ce-rc1

View File

@ -1,8 +0,0 @@
# Github code owners
# See https://github.com/blog/2392-introducing-code-owners
cli/compose/** @dnephin @vdemeester
contrib/completion/bash/** @albers
contrib/completion/zsh/** @sdurrheimer
docs/** @mstanleyjones @vdemeester @thaJeztah
scripts/** @dnephin

View File

@ -2,9 +2,3 @@
/build/
cli/winresources/rsrc_386.syso
cli/winresources/rsrc_amd64.syso
/man/man1/
/man/man5/
/man/man8/
/docs/yaml/gen/
coverage.txt
profile.out

View File

@ -1,66 +1,49 @@
#
# github.com/docker/cli
#
all: binary
# remove build artifacts
.PHONY: clean
clean:
rm -rf ./build/* cli/winresources/rsrc_* ./man/man[1-9] docs/yaml/gen
@rm -rf ./build/* cli/winresources/rsrc_*
# run go test
# the "-tags daemon" part is temporary
.PHONY: test
test:
./scripts/test/unit $(shell go list ./... | grep -v '/vendor/')
.PHONY: test-coverage
test-coverage:
./scripts/test/unit-with-coverage $(shell go list ./... | grep -v '/vendor/')
@go test -tags daemon -v $(shell go list ./... | grep -v /vendor/)
.PHONY: lint
lint:
gometalinter --config gometalinter.json ./...
@gometalinter --config gometalinter.json ./...
.PHONY: binary
binary:
@echo "WARNING: binary creates a Linux executable. Use cross for macOS or Windows."
./scripts/build/binary
@./scripts/build/binary
# build the CLI for multiple architectures
.PHONY: cross
cross:
./scripts/build/cross
@./scripts/build/cross
.PHONY: dynbinary
dynbinary:
./scripts/build/dynbinary
@./scripts/build/dynbinary
.PHONY: watch
watch:
./scripts/test/watch
@./scripts/test/watch
# Check vendor matches vendor.conf
# download dependencies (vendor/) listed in vendor.conf
.PHONY: vendor
vendor: vendor.conf
vndr 2> /dev/null
scripts/validate/check-git-diff vendor
## generate man pages from go source and markdown
.PHONY: manpages
manpages:
scripts/docs/generate-man.sh
## generate documentation YAML files consumed by docs repo
.PHONY: yamldocs
yamldocs:
scripts/docs/generate-yaml.sh
## Shellcheck validation
.PHONY: shellcheck
shellcheck:
scripts/validate/shellcheck
@vndr 2> /dev/null
@scripts/validate/check-git-diff vendor
cli/compose/schema/bindata.go: cli/compose/schema/data/*.json
go generate github.com/docker/cli/cli/compose/schema
compose-jsonschema: cli/compose/schema/bindata.go
scripts/validate/check-git-diff cli/compose/schema/bindata.go
@scripts/validate/check-git-diff cli/compose/schema/bindata.go

View File

@ -1 +1 @@
17.07.0-ce-rc2
17.06.0-ce-rc1

View File

@ -1,115 +1,49 @@
version: 2
jobs:
lint:
build:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
docker:
- image: docker:17.05
parallelism: 4
steps:
- checkout
- setup_remote_docker:
reusable: true
exclusive: false
- run:
command: docker version
- run:
name: "Lint"
command: |
dockerfile=dockerfiles/Dockerfile.lint
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-linter:$CIRCLE_BUILD_NUM .
docker run --rm cli-linter:$CIRCLE_BUILD_NUM
cross:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
parallelism: 3
steps:
- checkout
- setup_remote_docker:
reusable: true
exclusive: false
- run:
name: "Cross"
command: |
dockerfile=dockerfiles/Dockerfile.cross
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder:$CIRCLE_BUILD_NUM .
name=cross-$CIRCLE_BUILD_NUM-$CIRCLE_NODE_INDEX
docker run \
-e CROSS_GROUP=$CIRCLE_NODE_INDEX \
--name $name cli-builder:$CIRCLE_BUILD_NUM \
make cross
docker cp \
$name:/go/src/github.com/docker/cli/build \
/work/build
- store_artifacts:
path: /work/build
test:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
steps:
- checkout
- setup_remote_docker:
reusable: true
exclusive: false
- run:
name: "Unit Test with Coverage"
command: |
dockerfile=dockerfiles/Dockerfile.dev
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder:$CIRCLE_BUILD_NUM .
docker run --name \
test-$CIRCLE_BUILD_NUM cli-builder:$CIRCLE_BUILD_NUM \
make test-coverage
- run:
name: "Upload to Codecov"
command: |
docker cp \
test-$CIRCLE_BUILD_NUM:/go/src/github.com/docker/cli/coverage.txt \
coverage.txt
apk add -U bash curl
curl -s https://codecov.io/bash | bash
validate:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
steps:
- checkout
- setup_remote_docker:
reusable: true
exclusive: false
- run:
name: "Validate Vendor, Docs, and Code Generation"
command: |
dockerfile=dockerfiles/Dockerfile.dev
echo "COPY . ." >> $dockerfile
rm -f .dockerignore # include .git
docker build -f $dockerfile --tag cli-builder-with-git:$CIRCLE_BUILD_NUM .
docker run --rm cli-builder-with-git:$CIRCLE_BUILD_NUM \
make -B vendor compose-jsonschema manpages yamldocs
shellcheck:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
steps:
name: "Install Git and SSH"
command: apk add -U git openssh
- checkout
- setup_remote_docker
- run:
name: "Run shellcheck"
name: "Lint"
command: |
dockerfile=dockerfiles/Dockerfile.shellcheck
if [ "$CIRCLE_NODE_INDEX" != "0" ]; then exit; fi
dockerfile=dockerfiles/Dockerfile.lint
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-validator:$CIRCLE_BUILD_NUM .
docker run --rm cli-validator:$CIRCLE_BUILD_NUM \
make -B shellcheck
workflows:
version: 2
ci:
jobs:
- lint
- cross
- test
- validate
- shellcheck
docker build -f $dockerfile --tag cli-linter .
docker run cli-linter
- run:
name: "Cross"
command: |
if [ "$CIRCLE_NODE_INDEX" != "1" ]; then exit; fi
dockerfile=dockerfiles/Dockerfile.cross
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder .
docker run --name cross cli-builder make cross
docker cp cross:/go/src/github.com/docker/cli/build /work/build
- run:
name: "Unit Test"
command: |
if [ "$CIRCLE_NODE_INDEX" != "2" ]; then exit; fi
dockerfile=dockerfiles/Dockerfile.dev
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder .
docker run cli-builder make test
- run:
name: "Validate Vendor and Code Generation"
command: |
if [ "$CIRCLE_NODE_INDEX" != "3" ]; then exit; fi
dockerfile=dockerfiles/Dockerfile.dev
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder .
docker run cli-builder make -B vendor compose-jsonschema
- store_artifacts:
path: /work/build

View File

@ -1,35 +0,0 @@
package checkpoint
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
checkpointCreateFunc func(container string, options types.CheckpointCreateOptions) error
checkpointDeleteFunc func(container string, options types.CheckpointDeleteOptions) error
checkpointListFunc func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error)
}
func (cli *fakeClient) CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error {
if cli.checkpointCreateFunc != nil {
return cli.checkpointCreateFunc(container, options)
}
return nil
}
func (cli *fakeClient) CheckpointDelete(ctx context.Context, container string, options types.CheckpointDeleteOptions) error {
if cli.checkpointDeleteFunc != nil {
return cli.checkpointDeleteFunc(container, options)
}
return nil
}
func (cli *fakeClient) CheckpointList(ctx context.Context, container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
if cli.checkpointListFunc != nil {
return cli.checkpointListFunc(container, options)
}
return []types.Checkpoint{}, nil
}

View File

@ -1,72 +0,0 @@
package checkpoint
import (
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestCheckpointCreateErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointCreateFunc func(container string, options types.CheckpointCreateOptions) error
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"foo", "bar"},
checkpointCreateFunc: func(container string, options types.CheckpointCreateOptions) error {
return errors.Errorf("error creating checkpoint for container foo")
},
expectedError: "error creating checkpoint for container foo",
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{
checkpointCreateFunc: tc.checkpointCreateFunc,
})
cmd := newCreateCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestCheckpointCreateWithOptions(t *testing.T) {
var containerID, checkpointID, checkpointDir string
var exit bool
cli := test.NewFakeCli(&fakeClient{
checkpointCreateFunc: func(container string, options types.CheckpointCreateOptions) error {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir
exit = options.Exit
return nil
},
})
cmd := newCreateCommand(cli)
checkpoint := "checkpoint-bar"
cmd.SetArgs([]string{"container-foo", checkpoint})
cmd.Flags().Set("leave-running", "true")
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "container-foo", containerID)
assert.Equal(t, checkpoint, checkpointID)
assert.Equal(t, "/dir/foo", checkpointDir)
assert.Equal(t, false, exit)
assert.Equal(t, checkpoint, strings.TrimSpace(cli.OutBuffer().String()))
}

View File

@ -1,71 +0,0 @@
package checkpoint
import (
"bytes"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestCheckpointListErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointListFunc func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error)
expectedError string
}{
{
args: []string{},
expectedError: "requires exactly 1 argument",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires exactly 1 argument",
},
{
args: []string{"foo"},
checkpointListFunc: func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
return []types.Checkpoint{}, errors.Errorf("error getting checkpoints for container foo")
},
expectedError: "error getting checkpoints for container foo",
},
}
for _, tc := range testCases {
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointListFunc: tc.checkpointListFunc,
}, &bytes.Buffer{})
cmd := newListCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestCheckpointListWithOptions(t *testing.T) {
var containerID, checkpointDir string
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointListFunc: func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
containerID = container
checkpointDir = options.CheckpointDir
return []types.Checkpoint{
{Name: "checkpoint-foo"},
}, nil
},
}, buf)
cmd := newListCommand(cli)
cmd.SetArgs([]string{"container-foo"})
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "container-foo", containerID)
assert.Equal(t, "/dir/foo", checkpointDir)
actual := buf.String()
expected := golden.Get(t, []byte(actual), "checkpoint-list-with-options.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -1,66 +0,0 @@
package checkpoint
import (
"bytes"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestCheckpointRemoveErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointDeleteFunc func(container string, options types.CheckpointDeleteOptions) error
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"foo", "bar"},
checkpointDeleteFunc: func(container string, options types.CheckpointDeleteOptions) error {
return errors.Errorf("error deleting checkpoint")
},
expectedError: "error deleting checkpoint",
},
}
for _, tc := range testCases {
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointDeleteFunc: tc.checkpointDeleteFunc,
}, &bytes.Buffer{})
cmd := newRemoveCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestCheckpointRemoveWithOptions(t *testing.T) {
var containerID, checkpointID, checkpointDir string
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointDeleteFunc: func(container string, options types.CheckpointDeleteOptions) error {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir
return nil
},
}, &bytes.Buffer{})
cmd := newRemoveCommand(cli)
cmd.SetArgs([]string{"container-foo", "checkpoint-bar"})
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "container-foo", containerID)
assert.Equal(t, "checkpoint-bar", checkpointID)
assert.Equal(t, "/dir/foo", checkpointDir)
}

View File

@ -1,2 +0,0 @@
CHECKPOINT NAME
checkpoint-foo

View File

@ -1,19 +1,21 @@
package command
import (
"fmt"
"io"
"net"
"net/http"
"os"
"runtime"
"time"
"github.com/docker/cli/cli"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/credentials"
cliflags "github.com/docker/cli/cli/flags"
dopts "github.com/docker/cli/opts"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
@ -38,7 +40,7 @@ type Cli interface {
In() *InStream
SetIn(in *InStream)
ConfigFile() *configfile.ConfigFile
ServerInfo() ServerInfo
CredentialsStore(serverAddress string) credentials.Store
}
// DockerCli is an instance the docker command line client.
@ -103,10 +105,59 @@ func (cli *DockerCli) ServerInfo() ServerInfo {
return cli.server
}
// GetAllCredentials returns all of the credentials stored in all of the
// configured credential stores.
func (cli *DockerCli) GetAllCredentials() (map[string]types.AuthConfig, error) {
auths := make(map[string]types.AuthConfig)
for registry := range cli.configFile.CredentialHelpers {
helper := cli.CredentialsStore(registry)
newAuths, err := helper.GetAll()
if err != nil {
return nil, err
}
addAll(auths, newAuths)
}
defaultStore := cli.CredentialsStore("")
newAuths, err := defaultStore.GetAll()
if err != nil {
return nil, err
}
addAll(auths, newAuths)
return auths, nil
}
func addAll(to, from map[string]types.AuthConfig) {
for reg, ac := range from {
to[reg] = ac
}
}
// CredentialsStore returns a new credentials store based
// on the settings provided in the configuration file. Empty string returns
// the default credential store.
func (cli *DockerCli) CredentialsStore(serverAddress string) credentials.Store {
if helper := getConfiguredCredentialStore(cli.configFile, serverAddress); helper != "" {
return credentials.NewNativeStore(cli.configFile, helper)
}
return credentials.NewFileStore(cli.configFile)
}
// getConfiguredCredentialStore returns the credential helper configured for the
// given registry, the default credsStore, or the empty string if neither are
// configured.
func getConfiguredCredentialStore(c *configfile.ConfigFile, serverAddress string) string {
if c.CredentialHelpers != nil && serverAddress != "" {
if helper, exists := c.CredentialHelpers[serverAddress]; exists {
return helper
}
}
return c.CredentialsStore
}
// Initialize the dockerCli runs initialization that must happen after command
// line flags are parsed.
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
cli.configFile = cliconfig.LoadDefaultConfigFile(cli.err)
cli.configFile = LoadDefaultConfigFile(cli.err)
var err error
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
@ -142,10 +193,15 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
OSType: ping.OSType,
}
cli.client.NegotiateAPIVersionPing(ping)
} else {
// Default to true if we fail to connect to daemon
cli.server = ServerInfo{HasExperimental: true}
// since the new header was added in 1.25, assume server is 1.24 if header is not present.
if ping.APIVersion == "" {
ping.APIVersion = "1.24"
}
// if server version is lower than the current cli, downgrade
if versions.LessThan(ping.APIVersion, cli.client.ClientVersion()) {
cli.client.UpdateClientVersion(ping.APIVersion)
}
}
return nil
@ -163,6 +219,19 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
}
// LoadDefaultConfigFile attempts to load the default config file and returns
// an initialized ConfigFile struct if none is found.
func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
configFile, e := cliconfig.Load(cliconfig.Dir())
if e != nil {
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
}
if !configFile.ContainsAuth() {
credentials.DetectDefaultStore(configFile)
}
return configFile
}
// NewAPIClientFromFlags creates a new APIClient from command line flags
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
@ -216,10 +285,6 @@ func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, er
}
tr := &http.Transport{
TLSClientConfig: config,
DialContext: (&net.Dialer{
KeepAlive: 30 * time.Second,
Timeout: 30 * time.Second,
}).DialContext,
}
proto, addr, _, err := client.ParseHost(host)
if err != nil {

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/pkg/system"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -64,7 +65,7 @@ func runConfigCreate(dockerCli command.Cli, options createOptions) error {
spec := swarm.ConfigSpec{
Annotations: swarm.Annotations{
Name: options.name,
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
},
Data: configData,
}

View File

@ -1,6 +1,7 @@
package config
import (
"bytes"
"io/ioutil"
"path/filepath"
"reflect"
@ -40,10 +41,11 @@ func TestConfigCreateErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigCreateCommand(
test.NewFakeCli(&fakeClient{
configCreateFunc: tc.configCreateFunc,
}),
}, buf),
)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
@ -53,6 +55,7 @@ func TestConfigCreateErrors(t *testing.T) {
func TestConfigCreateWithName(t *testing.T) {
name := "foo"
buf := new(bytes.Buffer)
var actual []byte
cli := test.NewFakeCli(&fakeClient{
configCreateFunc: func(spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
@ -66,14 +69,14 @@ func TestConfigCreateWithName(t *testing.T) {
ID: "ID-" + spec.Name,
}, nil
},
})
}, buf)
cmd := newConfigCreateCommand(cli)
cmd.SetArgs([]string{name, filepath.Join("testdata", configDataFile)})
assert.NoError(t, cmd.Execute())
expected := golden.Get(t, actual, configDataFile)
assert.Equal(t, string(expected), string(actual))
assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String()))
assert.Equal(t, "ID-"+name, strings.TrimSpace(buf.String()))
}
func TestConfigCreateWithLabels(t *testing.T) {
@ -83,6 +86,7 @@ func TestConfigCreateWithLabels(t *testing.T) {
}
name := "foo"
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configCreateFunc: func(spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
if spec.Name != name {
@ -97,12 +101,12 @@ func TestConfigCreateWithLabels(t *testing.T) {
ID: "ID-" + spec.Name,
}, nil
},
})
}, buf)
cmd := newConfigCreateCommand(cli)
cmd.SetArgs([]string{name, filepath.Join("testdata", configDataFile)})
cmd.Flags().Set("label", "lbl1=Label-foo")
cmd.Flags().Set("label", "lbl2=Label-bar")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String()))
assert.Equal(t, "ID-"+name, strings.TrimSpace(buf.String()))
}

View File

@ -55,7 +55,7 @@ func TestConfigInspectErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf),
)
@ -97,7 +97,7 @@ func TestConfigInspectWithoutFormat(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf),
)
@ -137,7 +137,7 @@ func TestConfigInspectWithFormat(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf),
)
@ -174,7 +174,7 @@ func TestConfigInspectPretty(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf))
cmd.SetArgs([]string{"configID"})

View File

@ -1,6 +1,7 @@
package config
import (
"bytes"
"io/ioutil"
"testing"
"time"
@ -35,10 +36,11 @@ func TestConfigListErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigListCommand(
test.NewFakeCli(&fakeClient{
configListFunc: tc.configListFunc,
}),
}, buf),
)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
@ -47,6 +49,7 @@ func TestConfigListErrors(t *testing.T) {
}
func TestConfigList(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -64,16 +67,18 @@ func TestConfigList(t *testing.T) {
),
}, nil
},
})
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newConfigListCommand(cli)
cmd.SetOutput(cli.OutBuffer())
cmd.SetOutput(buf)
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
actual := buf.String()
expected := golden.Get(t, []byte(actual), "config-list.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithQuietOption(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -83,16 +88,18 @@ func TestConfigListWithQuietOption(t *testing.T) {
})),
}, nil
},
})
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newConfigListCommand(cli)
cmd.Flags().Set("quiet", "true")
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
actual := buf.String()
expected := golden.Get(t, []byte(actual), "config-list-with-quiet-option.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithConfigFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -102,18 +109,19 @@ func TestConfigListWithConfigFormat(t *testing.T) {
})),
}, nil
},
})
cli.SetConfigFile(&configfile.ConfigFile{
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{
ConfigFormat: "{{ .Name }} {{ .Labels }}",
})
cmd := newConfigListCommand(cli)
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
actual := buf.String()
expected := golden.Get(t, []byte(actual), "config-list-with-config-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -123,16 +131,17 @@ func TestConfigListWithFormat(t *testing.T) {
})),
}, nil
},
})
}, buf)
cmd := newConfigListCommand(cli)
cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}")
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
actual := buf.String()
expected := golden.Get(t, []byte(actual), "config-list-with-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithFilter(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
assert.Equal(t, "foo", options.Filters.Get("name")[0])
@ -152,12 +161,13 @@ func TestConfigListWithFilter(t *testing.T) {
),
}, nil
},
})
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newConfigListCommand(cli)
cmd.Flags().Set("filter", "name=foo")
cmd.Flags().Set("filter", "label=lbl1=Label-bar")
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
actual := buf.String()
expected := golden.Get(t, []byte(actual), "config-list-with-filter.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -33,7 +33,7 @@ func TestConfigRemoveErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigRemoveCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
configRemoveFunc: tc.configRemoveFunc,
}, buf),
)
@ -47,7 +47,7 @@ func TestConfigRemoveWithName(t *testing.T) {
names := []string{"foo", "bar"}
buf := new(bytes.Buffer)
var removedConfigs []string
cli := test.NewFakeCliWithOutput(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
configRemoveFunc: func(name string) error {
removedConfigs = append(removedConfigs, name)
return nil
@ -65,7 +65,7 @@ func TestConfigRemoveContinueAfterError(t *testing.T) {
buf := new(bytes.Buffer)
var removedConfigs []string
cli := test.NewFakeCliWithOutput(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
configRemoveFunc: func(name string) error {
removedConfigs = append(removedConfigs, name)
if name == "foo" {
@ -77,7 +77,6 @@ func TestConfigRemoveContinueAfterError(t *testing.T) {
cmd := newConfigRemoveCommand(cli)
cmd.SetArgs(names)
cmd.SetOutput(ioutil.Discard)
assert.EqualError(t, cmd.Execute(), "error removing config: foo")
assert.Equal(t, names, removedConfigs)
}

View File

@ -8,7 +8,6 @@ import (
"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/spf13/cobra"
@ -23,26 +22,8 @@ type attachOptions struct {
container string
}
func inspectContainerAndCheckState(ctx context.Context, cli client.APIClient, args string) (*types.ContainerJSON, error) {
c, err := cli.ContainerInspect(ctx, args)
if err != nil {
return nil, err
}
if !c.State.Running {
return nil, errors.New("You cannot attach to a stopped container, start it first")
}
if c.State.Paused {
return nil, errors.New("You cannot attach to a paused container, unpause it first")
}
if c.State.Restarting {
return nil, errors.New("You cannot attach to a restarting container, wait until it is running")
}
return &c, nil
}
// NewAttachCommand creates a new cobra.Command for `docker attach`
func NewAttachCommand(dockerCli command.Cli) *cobra.Command {
func NewAttachCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts attachOptions
cmd := &cobra.Command{
@ -62,15 +43,23 @@ func NewAttachCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runAttach(dockerCli command.Cli, opts *attachOptions) error {
func runAttach(dockerCli *command.DockerCli, opts *attachOptions) error {
ctx := context.Background()
client := dockerCli.Client()
c, err := inspectContainerAndCheckState(ctx, client, opts.container)
c, err := client.ContainerInspect(ctx, opts.container)
if err != nil {
return err
}
if !c.State.Running {
return errors.New("You cannot attach to a stopped container, start it first")
}
if c.State.Paused {
return errors.New("You cannot attach to a paused container, unpause it first")
}
if err := dockerCli.In().CheckTty(!opts.noStdin, c.Config.Tty); err != nil {
return err
}
@ -106,19 +95,6 @@ func runAttach(dockerCli command.Cli, opts *attachOptions) error {
}
defer resp.Close()
// If use docker attach command to attach to a stop container, it will return
// "You cannot attach to a stopped container" error, it's ok, but when
// attach to a running container, it(docker attach) use inspect to check
// the container's state, if it pass the state check on the client side,
// and then the container is stopped, docker attach command still attach to
// the container and not exit.
//
// Recheck the container's state to avoid attach block.
_, err = inspectContainerAndCheckState(ctx, client, opts.container)
if err != nil {
return err
}
if c.Config.Tty && dockerCli.Out().IsTerminal() {
height, width := dockerCli.Out().GetTtySize()
// To handle the case where a user repeatedly attaches/detaches without resizing their

View File

@ -1,75 +0,0 @@
package container
import (
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
)
func TestNewAttachCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
containerInspectFunc func(img string) (types.ContainerJSON, error)
}{
{
name: "client-error",
args: []string{"5cb5bb5e4a3b"},
expectedError: "something went wrong",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
return types.ContainerJSON{}, errors.Errorf("something went wrong")
},
},
{
name: "client-stopped",
args: []string{"5cb5bb5e4a3b"},
expectedError: "You cannot attach to a stopped container",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
c := types.ContainerJSON{}
c.ContainerJSONBase = &types.ContainerJSONBase{}
c.ContainerJSONBase.State = &types.ContainerState{Running: false}
return c, nil
},
},
{
name: "client-paused",
args: []string{"5cb5bb5e4a3b"},
expectedError: "You cannot attach to a paused container",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
c := types.ContainerJSON{}
c.ContainerJSONBase = &types.ContainerJSONBase{}
c.ContainerJSONBase.State = &types.ContainerState{
Running: true,
Paused: true,
}
return c, nil
},
},
{
name: "client-restarting",
args: []string{"5cb5bb5e4a3b"},
expectedError: "You cannot attach to a restarting container",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
c := types.ContainerJSON{}
c.ContainerJSONBase = &types.ContainerJSONBase{}
c.ContainerJSONBase.State = &types.ContainerState{
Running: true,
Paused: false,
Restarting: true,
}
return c, nil
},
},
}
for _, tc := range testCases {
cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc}))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}

View File

@ -1,19 +0,0 @@
package container
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
containerInspectFunc func(string) (types.ContainerJSON, error)
}
func (cli *fakeClient) ContainerInspect(_ context.Context, containerID string) (types.ContainerJSON, error) {
if cli.containerInspectFunc != nil {
return cli.containerInspectFunc(containerID)
}
return types.ContainerJSON{}, nil
}

View File

@ -33,7 +33,7 @@ func newExecOptions() *execOptions {
}
// NewExecCommand creates a new cobra.Command for `docker exec`
func NewExecCommand(dockerCli command.Cli) *cobra.Command {
func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command {
options := newExecOptions()
cmd := &cobra.Command{
@ -63,7 +63,7 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
}
// nolint: gocyclo
func runExec(dockerCli command.Cli, options *execOptions, container string, execCmd []string) error {
func runExec(dockerCli *command.DockerCli, options *execOptions, container string, execCmd []string) error {
execConfig, err := parseExec(options, execCmd)
// just in case the ParseExec does not exit
if container == "" || err != nil {
@ -111,7 +111,12 @@ func runExec(dockerCli command.Cli, options *execOptions, container string, exec
Tty: execConfig.Tty,
}
return client.ContainerExecStart(ctx, execID, execStartCheck)
if err := client.ContainerExecStart(ctx, execID, execStartCheck); err != nil {
return err
}
// For now don't print this - wait for when we support exec wait()
// fmt.Fprintf(dockerCli.Out(), "%s\n", execID)
return nil
}
// Interactive exec requested.

View File

@ -1,14 +1,9 @@
package container
import (
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
type arguments struct {
@ -78,7 +73,9 @@ func TestParseExec(t *testing.T) {
for valid, expectedExecConfig := range valids {
execConfig, err := parseExec(&valid.options, valid.execCmd)
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
if !compareExecConfig(expectedExecConfig, execConfig) {
t.Fatalf("Expected [%v] for %v, got [%v]", expectedExecConfig, valid, execConfig)
}
@ -117,28 +114,3 @@ func compareExecConfig(config1 *types.ExecConfig, config2 *types.ExecConfig) boo
}
return true
}
func TestNewExecCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
containerInspectFunc func(img string) (types.ContainerJSON, error)
}{
{
name: "client-error",
args: []string{"5cb5bb5e4a3b", "-t", "-i", "bash"},
expectedError: "something went wrong",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
return types.ContainerJSON{}, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc})
cmd := NewExecCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}

View File

@ -110,7 +110,7 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
func (h *hijackedIOStreamer) beginOutputStream(restoreInput func()) <-chan error {
if h.outputStream == nil && h.errorStream == nil {
// There is no need to copy output.
// Ther is no need to copy output.
return nil
}
@ -163,7 +163,7 @@ func (h *hijackedIOStreamer) beginInputStream(restoreInput func()) (doneC <-chan
if err != nil {
// This error will also occur on the receive
// side (from stdout) where it will be
// propagated back to the caller.
// propogated back to the caller.
logrus.Debugf("Error sendStdin: %s", err)
}
}

View File

@ -12,19 +12,19 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/pkg/signal"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/spf13/pflag"
)
var (
deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`)
deviceCgroupRuleRegexp = regexp.MustCompile("^[acb] ([0-9]+|\\*):([0-9]+|\\*) [rwm]{1,3}$")
)
// containerOptions is a data object with all the options for creating a container
@ -333,8 +333,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
volumes := copts.volumes.GetMap()
// add any bind targets to the list of container volumes
for bind := range copts.volumes.GetMap() {
parsed, _ := loader.ParseVolume(bind)
if parsed.Source != "" {
if arr := volumeSplitN(bind, 2); len(arr) > 1 {
// after creating the bind mount we want to delete it from the copts.volumes values because
// we do not want bind mounts being committed to image configs
binds = append(binds, bind)
@ -410,13 +409,13 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
}
// collect all the environment variables for the container
envVariables, err := opts.ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll())
envVariables, err := runconfigopts.ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll())
if err != nil {
return nil, err
}
// collect all the labels for the container
labels, err := opts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll())
labels, err := runconfigopts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll())
if err != nil {
return nil, err
}
@ -441,7 +440,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
return nil, errors.Errorf("--userns: invalid USER mode")
}
restartPolicy, err := opts.ParseRestartPolicy(copts.restartPolicy)
restartPolicy, err := runconfigopts.ParseRestartPolicy(copts.restartPolicy)
if err != nil {
return nil, err
}
@ -554,7 +553,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
MacAddress: copts.macAddress,
Entrypoint: entrypoint,
WorkingDir: copts.workingDir,
Labels: opts.ConvertKVStringsToMap(labels),
Labels: runconfigopts.ConvertKVStringsToMap(labels),
Healthcheck: healthConfig,
}
if flags.Changed("stop-signal") {
@ -667,7 +666,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
}
func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts)
loggingOptsMap := runconfigopts.ConvertKVStringsToMap(loggingOpts)
if loggingDriver == "none" && len(loggingOpts) > 0 {
return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", loggingDriver)
}
@ -829,6 +828,67 @@ func validatePath(val string, validator func(string) bool) (string, error) {
return val, nil
}
// volumeSplitN splits raw into a maximum of n parts, separated by a separator colon.
// A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
// In Windows driver letter appears in two situations:
// a. `^[a-zA-Z]:` (A colon followed by `^[a-zA-Z]:` is OK as colon is the separator in volume option)
// b. A string in the format like `\\?\C:\Windows\...` (UNC).
// Therefore, a driver letter can only follow either a `:` or `\\`
// This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`.
func volumeSplitN(raw string, n int) []string {
var array []string
if len(raw) == 0 || raw[0] == ':' {
// invalid
return nil
}
// numberOfParts counts the number of parts separated by a separator colon
numberOfParts := 0
// left represents the left-most cursor in raw, updated at every `:` character considered as a separator.
left := 0
// right represents the right-most cursor in raw incremented with the loop. Note this
// starts at index 1 as index 0 is already handle above as a special case.
for right := 1; right < len(raw); right++ {
// stop parsing if reached maximum number of parts
if n >= 0 && numberOfParts >= n {
break
}
if raw[right] != ':' {
continue
}
potentialDriveLetter := raw[right-1]
if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
if right > 1 {
beforePotentialDriveLetter := raw[right-2]
// Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`)
if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' {
// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
array = append(array, raw[left:right])
left = right + 1
numberOfParts++
}
// else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing.
}
// if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing.
} else {
// if `:` is not preceded by a potential drive letter, then consider it as a delimiter.
array = append(array, raw[left:right])
left = right + 1
numberOfParts++
}
}
// need to take care of the last part
if left < len(raw) {
if n >= 0 && numberOfParts >= n {
// if the maximum number of parts is reached, just append the rest to the last part
// left-1 is at the last `:` that needs to be included since not considered a separator.
array[n-1] += raw[left-1:]
} else {
array = append(array, raw[left:])
}
}
return array
}
// validateAttach validates that the specified string is a valid attach option.
func validateAttach(val string) (string, error) {
s := strings.ToLower(val)

View File

@ -19,7 +19,6 @@ import (
"github.com/pkg/errors"
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestValidateAttach(t *testing.T) {
@ -62,14 +61,16 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *network
return containerConfig.Config, containerConfig.HostConfig, containerConfig.NetworkingConfig, err
}
func parseMustError(t *testing.T, args string) {
_, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
assert.Error(t, err, args)
func parsetest(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
return config, hostConfig, err
}
func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
config, hostConfig, _, err := parseRun(append(strings.Split(args, " "), "ubuntu", "bash"))
assert.NoError(t, err)
config, hostConfig, err := parsetest(t, args)
if err != nil {
t.Fatal(err)
}
return config, hostConfig
}
@ -85,6 +86,7 @@ func TestParseRunLinks(t *testing.T) {
}
}
// nolint: gocyclo
func TestParseRunAttach(t *testing.T) {
if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
@ -101,17 +103,31 @@ func TestParseRunAttach(t *testing.T) {
if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
}
}
func TestParseRunWithInvalidArgs(t *testing.T) {
parseMustError(t, "-a")
parseMustError(t, "-a invalid")
parseMustError(t, "-a invalid -a stdout")
parseMustError(t, "-a stdout -a stderr -d")
parseMustError(t, "-a stdin -d")
parseMustError(t, "-a stdout -d")
parseMustError(t, "-a stderr -d")
parseMustError(t, "-d --rm")
if _, _, err := parsetest(t, "-a"); err == nil {
t.Fatal("Error parsing attach flags, `-a` should be an error but is not")
}
if _, _, err := parsetest(t, "-a invalid"); err == nil {
t.Fatal("Error parsing attach flags, `-a invalid` should be an error but is not")
}
if _, _, err := parsetest(t, "-a invalid -a stdout"); err == nil {
t.Fatal("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
}
if _, _, err := parsetest(t, "-a stdout -a stderr -d"); err == nil {
t.Fatal("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
}
if _, _, err := parsetest(t, "-a stdin -d"); err == nil {
t.Fatal("Error parsing attach flags, `-a stdin -d` should be an error but is not")
}
if _, _, err := parsetest(t, "-a stdout -d"); err == nil {
t.Fatal("Error parsing attach flags, `-a stdout -d` should be an error but is not")
}
if _, _, err := parsetest(t, "-a stderr -d"); err == nil {
t.Fatal("Error parsing attach flags, `-a stderr -d` should be an error but is not")
}
if _, _, err := parsetest(t, "-d --rm"); err == nil {
t.Fatal("Error parsing attach flags, `-d --rm` should be an error but is not")
}
}
// nolint: gocyclo
@ -367,46 +383,51 @@ func TestParseDevice(t *testing.T) {
func TestParseModes(t *testing.T) {
// ipc ko
_, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"})
testutil.ErrorContains(t, err, "--ipc: invalid IPC mode")
if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
}
// ipc ok
_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
if !hostconfig.IpcMode.Valid() {
t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
}
// pid ko
_, _, _, err = parseRun([]string{"--pid=container:", "img", "cmd"})
testutil.ErrorContains(t, err, "--pid: invalid PID mode")
if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
}
// pid ok
_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
if !hostconfig.PidMode.Valid() {
t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
}
// uts ko
_, _, _, err = parseRun([]string{"--uts=container:", "img", "cmd"})
testutil.ErrorContains(t, err, "--uts: invalid UTS mode")
if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
}
// uts ok
_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
if !hostconfig.UTSMode.Valid() {
t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
}
// shm-size ko
expectedErr := `invalid argument "a128m" for --shm-size=a128m: invalid size: 'a128m'`
_, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"})
testutil.ErrorContains(t, err, expectedErr)
if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != expectedErr {
t.Fatalf("Expected an error with message '%v', got %v", expectedErr, err)
}
// shm-size ok
_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
if hostconfig.ShmSize != 134217728 {
t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize)
}
@ -750,6 +771,58 @@ func callDecodeContainerConfig(volumes []string, binds []string) (*container.Con
return c, h, err
}
func TestVolumeSplitN(t *testing.T) {
for _, x := range []struct {
input string
n int
expected []string
}{
{`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}},
{`:C:\foo:d:`, -1, nil},
{`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}},
{`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}},
{`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}},
{`d:\`, -1, []string{`d:\`}},
{`d:`, -1, []string{`d:`}},
{`d:\path`, -1, []string{`d:\path`}},
{`d:\path with space`, -1, []string{`d:\path with space`}},
{`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}},
{`c:\:d:\`, -1, []string{`c:\`, `d:\`}},
{`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}},
{`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}},
{`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}},
{`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}},
{`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}},
{`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}},
{`name:D:`, -1, []string{`name`, `D:`}},
{`name:D::rW`, -1, []string{`name`, `D:`, `rW`}},
{`name:D::RW`, -1, []string{`name`, `D:`, `RW`}},
{`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}},
{`c:\Windows`, -1, []string{`c:\Windows`}},
{`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}},
{``, -1, nil},
{`.`, -1, []string{`.`}},
{`..\`, -1, []string{`..\`}},
{`c:\:..\`, -1, []string{`c:\`, `..\`}},
{`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
// Cover directories with one-character name
{`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}},
} {
res := volumeSplitN(x.input, x.n)
if len(res) < len(x.expected) {
t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
}
for i, e := range res {
if e != x.expected[i] {
t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
}
}
}
}
func TestValidateDevice(t *testing.T) {
valid := []string{
"/home",

View File

@ -5,7 +5,6 @@ import (
"io"
"net/http/httputil"
"os"
"regexp"
"runtime"
"strings"
"syscall"
@ -13,12 +12,12 @@ import (
"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/docker/libnetwork/resolvconf/dns"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -78,43 +77,21 @@ func warnOnOomKillDisable(hostConfig container.HostConfig, stderr io.Writer) {
// they are trying to set a DNS to a localhost address
func warnOnLocalhostDNS(hostConfig container.HostConfig, stderr io.Writer) {
for _, dnsIP := range hostConfig.DNS {
if isLocalhost(dnsIP) {
if dns.IsLocalhost(dnsIP) {
fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
return
}
}
}
// IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range.
const ipLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)`
var localhostIPRegexp = regexp.MustCompile(ipLocalhost)
// IsLocalhost returns true if ip matches the localhost IP regular expression.
// Used for determining if nameserver settings are being passed which are
// localhost addresses
func isLocalhost(ip string) bool {
return localhostIPRegexp.MatchString(ip)
}
func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), copts.env.GetAll())
newEnv := []string{}
for k, v := range proxyConfig {
if v == nil {
newEnv = append(newEnv, k)
} else {
newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v))
}
}
copts.env = *opts.NewListOptsRef(&newEnv, nil)
func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *containerOptions) error {
containerConfig, err := parse(flags, copts)
// just in case the parse does not exit
if err != nil {
reportError(dockerCli.Err(), "run", err.Error(), true)
return cli.StatusError{StatusCode: 125}
}
return runContainer(dockerCli, ropts, copts, containerConfig)
return runContainer(dockerCli, opts, copts, containerConfig)
}
// nolint: gocyclo
@ -170,7 +147,6 @@ func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *contain
sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID)
defer signal.StopCatch(sigc)
}
var (
waitDisplayID chan struct{}
errCh chan error
@ -190,11 +166,10 @@ func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *contain
}
close, err := attachContainer(ctx, dockerCli, &errCh, config, createResponse.ID)
defer close()
if err != nil {
return err
}
defer close()
}
statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, copts.autoRemove)

View File

@ -146,12 +146,12 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error {
fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
}
}
if attachErr := <-cErr; attachErr != nil {
if attchErr := <-cErr; attchErr != nil {
if _, ok := err.(term.EscapeError); ok {
// The user entered the detach escape sequence.
return nil
}
return attachErr
return attchErr
}
if status := <-statusChan; status != 0 {

View File

@ -106,7 +106,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
closeChan <- err
}
for _, container := range cs {
s := formatter.NewContainerStats(container.ID[:12])
s := formatter.NewContainerStats(container.ID[:12], daemonOSType)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
@ -123,7 +123,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
eh := command.InitEventHandler()
eh.Handle("create", func(e events.Message) {
if opts.all {
s := formatter.NewContainerStats(e.ID[:12])
s := formatter.NewContainerStats(e.ID[:12], daemonOSType)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
@ -132,7 +132,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
})
eh.Handle("start", func(e events.Message) {
s := formatter.NewContainerStats(e.ID[:12])
s := formatter.NewContainerStats(e.ID[:12], daemonOSType)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
@ -158,7 +158,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
// Artificially send creation events for the containers we were asked to
// monitor (same code path than we use when monitoring all containers).
for _, name := range opts.containers {
s := formatter.NewContainerStats(name)
s := formatter.NewContainerStats(name, daemonOSType)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)

View File

@ -16,8 +16,9 @@ import (
)
type stats struct {
mu sync.Mutex
cs []*formatter.ContainerStats
ostype string
mu sync.Mutex
cs []*formatter.ContainerStats
}
// daemonOSType is set once we have at least one stat for a container

View File

@ -39,7 +39,7 @@ func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id strin
}
// MonitorTtySize updates the container tty size when the terminal tty changes size
func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool) error {
func MonitorTtySize(ctx context.Context, cli *command.DockerCli, id string, isExec bool) error {
resizeTty := func() {
height, width := cli.Out().GetTtySize()
resizeTtyTo(ctx, cli.Client(), id, height, width, isExec)
@ -74,7 +74,7 @@ func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool
}
// ForwardAllSignals forwards signals to the container
func ForwardAllSignals(ctx context.Context, cli command.Cli, cid string) chan os.Signal {
func ForwardAllSignals(ctx context.Context, cli *command.DockerCli, cid string) chan os.Signal {
sigc := make(chan os.Signal, 128)
signal.CatchAll(sigc)
go func() {

View File

@ -8,6 +8,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
containertypes "github.com/docker/docker/api/types/container"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -81,7 +82,7 @@ func runUpdate(dockerCli *command.DockerCli, options *updateOptions) error {
var restartPolicy containertypes.RestartPolicy
if options.restartPolicy != "" {
restartPolicy, err = opts.ParseRestartPolicy(options.restartPolicy)
restartPolicy, err = runconfigopts.ParseRestartPolicy(options.restartPolicy)
if err != nil {
return err
}

View File

@ -127,7 +127,7 @@ func legacyWaitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli,
// getExitCode performs an inspect on the container. It returns
// the running state and the exit code.
func getExitCode(ctx context.Context, dockerCli command.Cli, containerID string) (bool, int, error) {
func getExitCode(ctx context.Context, dockerCli *command.DockerCli, containerID string) (bool, int, error) {
c, err := dockerCli.Client().ContainerInspect(ctx, containerID)
if err != nil {
// If we can't connect, then the daemon probably died.

View File

@ -2,16 +2,16 @@ package formatter
import (
"fmt"
"sort"
"strconv"
"strings"
"time"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/go-units"
units "github.com/docker/go-units"
)
const (
@ -171,16 +171,16 @@ func (c *containerContext) Command() string {
}
func (c *containerContext) CreatedAt() string {
return time.Unix(c.c.Created, 0).String()
return time.Unix(int64(c.c.Created), 0).String()
}
func (c *containerContext) RunningFor() string {
createdAt := time.Unix(c.c.Created, 0)
createdAt := time.Unix(int64(c.c.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
}
func (c *containerContext) Ports() string {
return DisplayablePorts(c.c.Ports)
return api.DisplayablePorts(c.c.Ports)
}
func (c *containerContext) Status() string {
@ -257,89 +257,3 @@ func (c *containerContext) Networks() string {
return strings.Join(networks, ",")
}
// DisplayablePorts returns formatted string representing open ports of container
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
// it's used by command 'docker ps'
func DisplayablePorts(ports []types.Port) string {
type portGroup struct {
first uint16
last uint16
}
groupMap := make(map[string]*portGroup)
var result []string
var hostMappings []string
var groupMapKeys []string
sort.Sort(byPortInfo(ports))
for _, port := range ports {
current := port.PrivatePort
portKey := port.Type
if port.IP != "" {
if port.PublicPort != current {
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
continue
}
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
}
group := groupMap[portKey]
if group == nil {
groupMap[portKey] = &portGroup{first: current, last: current}
// record order that groupMap keys are created
groupMapKeys = append(groupMapKeys, portKey)
continue
}
if current == (group.last + 1) {
group.last = current
continue
}
result = append(result, formGroup(portKey, group.first, group.last))
groupMap[portKey] = &portGroup{first: current, last: current}
}
for _, portKey := range groupMapKeys {
g := groupMap[portKey]
result = append(result, formGroup(portKey, g.first, g.last))
}
result = append(result, hostMappings...)
return strings.Join(result, ", ")
}
func formGroup(key string, start, last uint16) string {
parts := strings.Split(key, "/")
groupType := parts[0]
var ip string
if len(parts) > 1 {
ip = parts[0]
groupType = parts[1]
}
group := strconv.Itoa(int(start))
if start != last {
group = fmt.Sprintf("%s-%d", group, last)
}
if ip != "" {
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
}
return fmt.Sprintf("%s/%s", group, groupType)
}
// byPortInfo is a temporary type used to sort types.Port by its fields
type byPortInfo []types.Port
func (r byPortInfo) Len() int { return len(r) }
func (r byPortInfo) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r byPortInfo) Less(i, j int) bool {
if r[i].PrivatePort != r[j].PrivatePort {
return r[i].PrivatePort < r[j].PrivatePort
}
if r[i].IP != r[j].IP {
return r[i].IP < r[j].IP
}
if r[i].PublicPort != r[j].PublicPort {
return r[i].PublicPort < r[j].PublicPort
}
return r[i].Type < r[j].Type
}

View File

@ -11,7 +11,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestContainerPsContext(t *testing.T) {
@ -227,7 +226,7 @@ size: 0B
Context{Format: NewContainerFormat("{{.Image}}", false, true)},
"ubuntu\nubuntu\n",
},
// Special headers for customized table format
// Special headers for customerized 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)},
`CONTAINER ID IMAGE CREATED/STATUS/ PORTS .NAMES STATUS
@ -358,11 +357,12 @@ func TestContainerContextWriteJSON(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var m map[string]interface{}
err := json.Unmarshal([]byte(line), &m)
require.NoError(t, err, msg)
assert.Equal(t, expectedJSONs[i], m, msg)
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Equal(t, expectedJSONs[i], m)
}
}
@ -377,11 +377,12 @@ func TestContainerContextWriteJSONField(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var s string
err := json.Unmarshal([]byte(line), &s)
require.NoError(t, err, msg)
assert.Equal(t, containers[i].ID, s, msg)
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Equal(t, containers[i].ID, s)
}
}
@ -410,248 +411,3 @@ func TestContainerBackCompat(t *testing.T) {
buf.Reset()
}
}
type ports struct {
ports []types.Port
expected string
}
// nolint: lll
func TestDisplayablePorts(t *testing.T) {
cases := []ports{
{
[]types.Port{
{
PrivatePort: 9988,
Type: "tcp",
},
},
"9988/tcp"},
{
[]types.Port{
{
PrivatePort: 9988,
Type: "udp",
},
},
"9988/udp",
},
{
[]types.Port{
{
IP: "0.0.0.0",
PrivatePort: 9988,
Type: "tcp",
},
},
"0.0.0.0:0->9988/tcp",
},
{
[]types.Port{
{
PrivatePort: 9988,
PublicPort: 8899,
Type: "tcp",
},
},
"9988/tcp",
},
{
[]types.Port{
{
IP: "4.3.2.1",
PrivatePort: 9988,
PublicPort: 8899,
Type: "tcp",
},
},
"4.3.2.1:8899->9988/tcp",
},
{
[]types.Port{
{
IP: "4.3.2.1",
PrivatePort: 9988,
PublicPort: 9988,
Type: "tcp",
},
},
"4.3.2.1:9988->9988/tcp",
},
{
[]types.Port{
{
PrivatePort: 9988,
Type: "udp",
}, {
PrivatePort: 9988,
Type: "udp",
},
},
"9988/udp, 9988/udp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PublicPort: 9998,
PrivatePort: 9998,
Type: "udp",
}, {
IP: "1.2.3.4",
PublicPort: 9999,
PrivatePort: 9999,
Type: "udp",
},
},
"1.2.3.4:9998-9999->9998-9999/udp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PublicPort: 8887,
PrivatePort: 9998,
Type: "udp",
}, {
IP: "1.2.3.4",
PublicPort: 8888,
PrivatePort: 9999,
Type: "udp",
},
},
"1.2.3.4:8887->9998/udp, 1.2.3.4:8888->9999/udp",
},
{
[]types.Port{
{
PrivatePort: 9998,
Type: "udp",
}, {
PrivatePort: 9999,
Type: "udp",
},
},
"9998-9999/udp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PrivatePort: 6677,
PublicPort: 7766,
Type: "tcp",
}, {
PrivatePort: 9988,
PublicPort: 8899,
Type: "udp",
},
},
"9988/udp, 1.2.3.4:7766->6677/tcp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PrivatePort: 9988,
PublicPort: 8899,
Type: "udp",
}, {
IP: "1.2.3.4",
PrivatePort: 9988,
PublicPort: 8899,
Type: "tcp",
}, {
IP: "4.3.2.1",
PrivatePort: 2233,
PublicPort: 3322,
Type: "tcp",
},
},
"4.3.2.1:3322->2233/tcp, 1.2.3.4:8899->9988/tcp, 1.2.3.4:8899->9988/udp",
},
{
[]types.Port{
{
PrivatePort: 9988,
PublicPort: 8899,
Type: "udp",
}, {
IP: "1.2.3.4",
PrivatePort: 6677,
PublicPort: 7766,
Type: "tcp",
}, {
IP: "4.3.2.1",
PrivatePort: 2233,
PublicPort: 3322,
Type: "tcp",
},
},
"9988/udp, 4.3.2.1:3322->2233/tcp, 1.2.3.4:7766->6677/tcp",
},
{
[]types.Port{
{
PrivatePort: 80,
Type: "tcp",
}, {
PrivatePort: 1024,
Type: "tcp",
}, {
PrivatePort: 80,
Type: "udp",
}, {
PrivatePort: 1024,
Type: "udp",
}, {
IP: "1.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "tcp",
}, {
IP: "1.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "udp",
}, {
IP: "1.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "tcp",
}, {
IP: "1.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "udp",
}, {
IP: "2.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "tcp",
}, {
IP: "2.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "udp",
}, {
IP: "2.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "tcp",
}, {
IP: "2.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "udp",
},
},
"80/tcp, 80/udp, 1024/tcp, 1024/udp, 1.1.1.1:1024->80/tcp, 1.1.1.1:1024->80/udp, 2.1.1.1:1024->80/tcp, 2.1.1.1:1024->80/udp, 1.1.1.1:80->1024/tcp, 1.1.1.1:80->1024/udp, 2.1.1.1:80->1024/tcp, 2.1.1.1:80->1024/udp",
},
}
for _, port := range cases {
actual := DisplayablePorts(port.ports)
assert.Equal(t, port.expected, actual)
}
}

View File

@ -29,12 +29,11 @@ const (
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
type DiskUsageContext struct {
Context
Verbose bool
LayersSize int64
Images []*types.ImageSummary
Containers []*types.Container
Volumes []*types.Volume
BuilderSize int64
Verbose bool
LayersSize int64
Images []*types.ImageSummary
Containers []*types.Container
Volumes []*types.Volume
}
func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
@ -66,59 +65,49 @@ reclaimable: {{.Reclaimable}}
}
func (ctx *DiskUsageContext) Write() (err error) {
if ctx.Verbose {
return ctx.verboseWrite()
}
ctx.buffer = bytes.NewBufferString("")
ctx.preFormat()
if ctx.Verbose == false {
ctx.buffer = bytes.NewBufferString("")
ctx.preFormat()
tmpl, err := ctx.parseFormat()
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
totalSize: ctx.LayersSize,
images: ctx.Images,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
containers: ctx.Containers,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
volumes: ctx.Volumes,
})
if err != nil {
return err
}
diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
diskUsageContainersCtx.header = map[string]string{
"Type": typeHeader,
"TotalCount": totalHeader,
"Active": activeHeader,
"Size": sizeHeader,
"Reclaimable": reclaimableHeader,
}
ctx.postFormat(tmpl, &diskUsageContainersCtx)
tmpl, err := ctx.parseFormat()
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
totalSize: ctx.LayersSize,
images: ctx.Images,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
containers: ctx.Containers,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
volumes: ctx.Volumes,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageBuilderContext{
builderSize: ctx.BuilderSize,
})
if err != nil {
return err
}
diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
diskUsageContainersCtx.header = map[string]string{
"Type": typeHeader,
"TotalCount": totalHeader,
"Active": activeHeader,
"Size": sizeHeader,
"Reclaimable": reclaimableHeader,
}
ctx.postFormat(tmpl, &diskUsageContainersCtx)
return err
}
func (ctx *DiskUsageContext) verboseWrite() (err error) {
// First images
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
if err != nil {
@ -187,9 +176,6 @@ func (ctx *DiskUsageContext) verboseWrite() (err error) {
}
}
ctx.postFormat(tmpl, newVolumeContext())
// And build cache
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
return
}
@ -243,11 +229,12 @@ func (c *diskUsageImagesContext) Reclaimable() string {
if c.totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/c.totalSize)
}
return units.HumanSize(float64(reclaimable))
return fmt.Sprintf("%s", units.HumanSize(float64(reclaimable)))
}
type diskUsageContainersContext struct {
HeaderContext
verbose bool
containers []*types.Container
}
@ -305,11 +292,12 @@ func (c *diskUsageContainersContext) Reclaimable() string {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
}
return units.HumanSize(float64(reclaimable))
return fmt.Sprintf("%s", units.HumanSize(float64(reclaimable)))
}
type diskUsageVolumesContext struct {
HeaderContext
verbose bool
volumes []*types.Volume
}
@ -366,34 +354,5 @@ func (c *diskUsageVolumesContext) Reclaimable() string {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
}
return units.HumanSize(float64(reclaimable))
}
type diskUsageBuilderContext struct {
HeaderContext
builderSize int64
}
func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
return marshalJSON(c)
}
func (c *diskUsageBuilderContext) Type() string {
return "Build Cache"
}
func (c *diskUsageBuilderContext) TotalCount() string {
return ""
}
func (c *diskUsageBuilderContext) Active() string {
return ""
}
func (c *diskUsageBuilderContext) Size() string {
return units.HumanSize(float64(c.builderSize))
}
func (c *diskUsageBuilderContext) Reclaimable() string {
return c.Size()
return fmt.Sprintf("%s", units.HumanSize(float64(reclaimable)))
}

View File

@ -23,7 +23,6 @@ func TestDiskUsageContextFormatWrite(t *testing.T) {
Images 0 0 0B 0B
Containers 0 0 0B 0B
Local Volumes 0 0 0B 0B
Build Cache 0B 0B
`,
},
{
@ -39,9 +38,6 @@ CONTAINER ID IMAGE COMMAND LOCAL VOLUMES
Local Volumes space usage:
VOLUME NAME LINKS SIZE
Build cache usage: 0B
`,
},
// Errors
@ -74,7 +70,6 @@ Build cache usage: 0B
Images 0 0 0B 0B
Containers 0 0 0B 0B
Local Volumes 0 0 0B 0B
Build Cache 0B 0B
`,
},
{
@ -87,7 +82,6 @@ Build Cache 0B
Images 0
Containers 0
Local Volumes 0
Build Cache
`,
},
// Raw Format
@ -115,12 +109,6 @@ active: 0
size: 0B
reclaimable: 0B
type: Build Cache
total:
active:
size: 0B
reclaimable: 0B
`,
},
}

View File

@ -79,18 +79,21 @@ func (c *historyContext) ID() string {
}
func (c *historyContext) CreatedAt() string {
return units.HumanDuration(time.Now().UTC().Sub(time.Unix(c.h.Created, 0)))
var created string
created = units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(c.h.Created), 0)))
return created
}
func (c *historyContext) CreatedSince() string {
created := units.HumanDuration(time.Now().UTC().Sub(time.Unix(c.h.Created, 0)))
var created string
created = units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(c.h.Created), 0)))
return created + " ago"
}
func (c *historyContext) CreatedBy() string {
createdBy := strings.Replace(c.h.CreatedBy, "\t", " ", -1)
if c.trunc {
return stringutils.Ellipsis(createdBy, 45)
createdBy = stringutils.Ellipsis(createdBy, 45)
}
return createdBy
}

View File

@ -234,12 +234,12 @@ func (c *imageContext) Digest() string {
}
func (c *imageContext) CreatedSince() string {
createdAt := time.Unix(c.i.Created, 0)
createdAt := time.Unix(int64(c.i.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
}
func (c *imageContext) CreatedAt() string {
return time.Unix(c.i.Created, 0).String()
return time.Unix(int64(c.i.Created), 0).String()
}
func (c *imageContext) Size() string {

View File

@ -3,7 +3,6 @@ package formatter
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
"time"
@ -11,7 +10,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNetworkContext(t *testing.T) {
@ -185,11 +183,12 @@ func TestNetworkContextWriteJSON(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var m map[string]interface{}
err := json.Unmarshal([]byte(line), &m)
require.NoError(t, err, msg)
assert.Equal(t, expectedJSONs[i], m, msg)
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Equal(t, expectedJSONs[i], m)
}
}
@ -204,10 +203,11 @@ func TestNetworkContextWriteJSONField(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var s string
err := json.Unmarshal([]byte(line), &s)
require.NoError(t, err, msg)
assert.Equal(t, networks[i].ID, s, msg)
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Equal(t, networks[i].ID, s)
}
}

View File

@ -3,7 +3,6 @@ package formatter
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
@ -11,7 +10,6 @@ import (
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/pkg/stringid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNodeContext(t *testing.T) {
@ -250,11 +248,12 @@ func TestNodeContextWriteJSON(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var m map[string]interface{}
err := json.Unmarshal([]byte(line), &m)
require.NoError(t, err, msg)
assert.Equal(t, testcase.expected[i], m, msg)
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Equal(t, testcase.expected[i], m)
}
}
}
@ -270,11 +269,12 @@ func TestNodeContextWriteJSONField(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var s string
err := json.Unmarshal([]byte(line), &s)
require.NoError(t, err, msg)
assert.Equal(t, nodes[i].ID, s, msg)
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Equal(t, nodes[i].ID, s)
}
}

View File

@ -12,7 +12,7 @@ func (d *dummy) Func1() string {
return "Func1"
}
func (d *dummy) func2() string { // nolint: unused
func (d *dummy) func2() string {
return "func2(should not be marshalled)"
}

View File

@ -3,13 +3,11 @@ package formatter
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestServiceContextWrite(t *testing.T) {
@ -202,11 +200,12 @@ func TestServiceContextWriteJSON(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var m map[string]interface{}
err := json.Unmarshal([]byte(line), &m)
require.NoError(t, err, msg)
assert.Equal(t, expectedJSONs[i], m, msg)
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Equal(t, expectedJSONs[i], m)
}
}
func TestServiceContextWriteJSONField(t *testing.T) {
@ -230,10 +229,11 @@ func TestServiceContextWriteJSONField(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var s string
err := json.Unmarshal([]byte(line), &s)
require.NoError(t, err, msg)
assert.Equal(t, services[i].Spec.Name, s, msg)
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Equal(t, services[i].Spec.Name, s)
}
}

View File

@ -109,8 +109,10 @@ func NewStatsFormat(source, osType string) Format {
}
// NewContainerStats returns a new ContainerStats entity and sets in it the given name
func NewContainerStats(container string) *ContainerStats {
return &ContainerStats{StatsEntry: StatsEntry{Container: container}}
func NewContainerStats(container, osType string) *ContainerStats {
return &ContainerStats{
StatsEntry: StatsEntry{Container: container},
}
}
// ContainerStatsWrite renders the context for a list of containers statistics
@ -184,7 +186,7 @@ func (c *containerStatsContext) MemUsage() string {
return fmt.Sprintf("-- / --")
}
if c.os == winOSType {
return units.BytesSize(c.s.Memory)
return fmt.Sprintf("%s", units.BytesSize(c.s.Memory))
}
return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit))
}

View File

@ -3,14 +3,12 @@ package formatter
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestVolumeContext(t *testing.T) {
@ -155,11 +153,12 @@ func TestVolumeContextWriteJSON(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var m map[string]interface{}
err := json.Unmarshal([]byte(line), &m)
require.NoError(t, err, msg)
assert.Equal(t, expectedJSONs[i], m, msg)
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Equal(t, expectedJSONs[i], m)
}
}
@ -174,10 +173,11 @@ func TestVolumeContextWriteJSONField(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var s string
err := json.Unmarshal([]byte(line), &s)
require.NoError(t, err, msg)
assert.Equal(t, volumes[i].Name, s, msg)
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Equal(t, volumes[i].Name, s)
}
}

View File

@ -12,7 +12,6 @@ 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"
@ -26,6 +25,7 @@ import (
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/urlutil"
runconfigopts "github.com/docker/docker/runconfig/opts"
units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -62,7 +62,6 @@ type buildOptions struct {
squash bool
target string
imageIDFile string
stream bool
}
// dockerfileFromStdin returns true when the user specified that the Dockerfile
@ -77,20 +76,16 @@ func (o buildOptions) contextFromStdin() bool {
return o.context == "-"
}
func newBuildOptions() buildOptions {
// NewBuildCommand creates a new `docker build` command
func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
ulimits := make(map[string]*units.Ulimit)
return buildOptions{
options := buildOptions{
tags: opts.NewListOpts(validateTag),
buildArgs: opts.NewListOpts(opts.ValidateEnv),
ulimits: opts.NewUlimitOpt(&ulimits),
labels: opts.NewListOpts(opts.ValidateEnv),
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
}
}
// NewBuildCommand creates a new `docker build` command
func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
options := newBuildOptions()
cmd := &cobra.Command{
Use: "build [OPTIONS] PATH | URL | -",
@ -139,10 +134,6 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
flags.SetAnnotation("squash", "experimental", nil)
flags.SetAnnotation("squash", "version", []string{"1.25"})
flags.BoolVar(&options.stream, "stream", false, "Stream attaches to server to negotiate build context")
flags.SetAnnotation("stream", "experimental", nil)
flags.SetAnnotation("stream", "version", []string{"1.31"})
return cmd
}
@ -163,7 +154,7 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
}
// nolint: gocyclo
func runBuild(dockerCli command.Cli, options buildOptions) error {
func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
var (
buildCtx io.ReadCloser
dockerfileCtx io.ReadCloser
@ -173,7 +164,6 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
relDockerfile string
progBuff io.Writer
buildBuff io.Writer
remote string
)
if options.dockerfileFromStdin() {
@ -199,7 +189,6 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
switch {
case options.contextFromStdin():
// buildCtx is tar archive. if stdin was dockerfile then it is wrapped
buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
case isLocalDir(specifiedContext):
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
@ -223,8 +212,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
contextDir = tempDir
}
// read from a directory into tar archive
if buildCtx == nil && !options.stream {
if buildCtx == nil {
excludes, err := build.ReadDockerignore(contextDir)
if err != nil {
return err
@ -241,7 +229,13 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
}
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
compression := archive.Uncompressed
if options.compress {
compression = archive.Gzip
}
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
Compression: compression,
ExcludePatterns: excludes,
})
if err != nil {
@ -249,52 +243,24 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
}
}
// replace Dockerfile if it was added from stdin and there is archive context
if dockerfileCtx != nil && buildCtx != nil {
// replace Dockerfile if added dynamically
if dockerfileCtx != nil {
buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
if err != nil {
return err
}
}
// if streaming and dockerfile was not from stdin then read from file
// to the same reader that is usually stdin
if options.stream && dockerfileCtx == nil {
dockerfileCtx, err = os.Open(relDockerfile)
if err != nil {
return errors.Wrapf(err, "failed to open %s", relDockerfile)
}
defer dockerfileCtx.Close()
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
var resolvedTags []*resolvedTag
if command.IsTrusted() {
translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
return TrustedReference(ctx, dockerCli, ref, nil)
}
// if there is a tar wrapper, the dockerfile needs to be replaced inside it
if buildCtx != nil {
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls.
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags)
} else if dockerfileCtx != nil {
// if there was not archive context still do the possible replacements in Dockerfile
newDockerfile, _, err := rewriteDockerfileFrom(ctx, dockerfileCtx, translator)
if err != nil {
return err
}
dockerfileCtx = ioutil.NopCloser(bytes.NewBuffer(newDockerfile))
}
}
if options.compress {
buildCtx, err = build.Compress(buildCtx)
if err != nil {
return err
}
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls.
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags)
}
// Setup an upload progress bar
@ -303,46 +269,9 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
progressOutput = &lastProgressOutput{output: progressOutput}
}
// if up to this point nothing has set the context then we must have have
// another way for sending it(streaming) and set the context to the Dockerfile
if dockerfileCtx != nil && buildCtx == nil {
buildCtx = dockerfileCtx
}
var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
s, err := trySession(dockerCli, contextDir)
if err != nil {
return err
}
var body io.Reader
if buildCtx != nil && !options.stream {
body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
}
// add context stream to the session
if options.stream && s != nil {
syncDone := make(chan error) // used to signal first progress reporting completed.
// progress would also send errors but don't need it here as errors
// are handled by session.Run() and ImageBuild()
if err := addDirToSession(s, contextDir, progressOutput, syncDone); err != nil {
return err
}
buf := newBufferedWriter(syncDone, buildBuff)
defer func() {
select {
case <-buf.flushed:
case <-ctx.Done():
}
}()
buildBuff = buf
remote = clientSessionRemote
body = buildCtx
}
configFile := dockerCli.ConfigFile()
authConfigs, _ := configFile.GetAllCredentials()
authConfigs, _ := dockerCli.GetAllCredentials()
buildOptions := types.ImageBuildOptions{
Memory: options.memory.Value(),
MemorySwap: options.memorySwap.Value(),
@ -362,27 +291,15 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
Dockerfile: relDockerfile,
ShmSize: options.shmSize.Value(),
Ulimits: options.ulimits.GetList(),
BuildArgs: configFile.ParseProxyConfig(dockerCli.Client().DaemonHost(), options.buildArgs.GetAll()),
BuildArgs: runconfigopts.ConvertKVStringsToMapWithNil(options.buildArgs.GetAll()),
AuthConfigs: authConfigs,
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
CacheFrom: options.cacheFrom,
SecurityOpt: options.securityOpt,
NetworkMode: options.networkMode,
Squash: options.squash,
ExtraHosts: options.extraHosts.GetAll(),
Target: options.target,
RemoteContext: remote,
}
if s != nil {
go func() {
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.UUID()
}
response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
@ -390,7 +307,6 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
if options.quiet {
fmt.Fprintf(dockerCli.Err(), "%s", progBuff)
}
cancel()
return err
}
defer response.Body.Close()

View File

@ -1,25 +1,25 @@
package build
import (
"archive/tar"
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"archive/tar"
"bytes"
"time"
"github.com/docker/docker/builder/remotecontext/git"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/gitutils"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/stringid"
@ -29,8 +29,6 @@ import (
const (
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
DefaultDockerfileName string = "Dockerfile"
// archiveHeaderSize is the number of bytes in an archive header
archiveHeaderSize = 512
)
// ValidateContextDirectory checks if all the contents of the directory
@ -87,12 +85,12 @@ func ValidateContextDirectory(srcPath string, excludes []string) error {
func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
buf := bufio.NewReader(r)
magic, err := buf.Peek(archiveHeaderSize)
magic, err := buf.Peek(archive.HeaderSize)
if err != nil && err != io.EOF {
return nil, "", errors.Errorf("failed to peek context header from STDIN: %v", err)
}
if IsArchive(magic) {
if archive.IsArchive(magic) {
return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
}
@ -136,18 +134,6 @@ func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCl
}
// IsArchive checks for the magic bytes of a tar or any supported compression
// algorithm.
func IsArchive(header []byte) bool {
compression := archive.DetectCompression(header)
if compression != archive.Uncompressed {
return true
}
r := tar.NewReader(bytes.NewBuffer(header))
_, err := r.Next()
return err == nil
}
// GetContextFromGitURL uses a Git URL as context for a `docker build`. The
// git repo is cloned into a temporary directory used as the context directory.
// Returns the absolute path to the temporary context directory, the relative
@ -157,7 +143,7 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error)
if _, err := exec.LookPath("git"); err != nil {
return "", "", errors.Wrapf(err, "unable to find 'git'")
}
absContextDir, err := git.Clone(gitURL)
absContextDir, err := gitutils.Clone(gitURL)
if err != nil {
return "", "", errors.Wrapf(err, "unable to 'git clone' to temporary context directory")
}
@ -175,7 +161,7 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error)
// Returns the tar archive used for the context and a path of the
// dockerfile inside the tar.
func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.ReadCloser, string, error) {
response, err := getWithStatusError(remoteURL)
response, err := httputils.Download(remoteURL)
if err != nil {
return nil, "", errors.Errorf("unable to download remote context %s: %v", remoteURL, err)
}
@ -187,24 +173,6 @@ func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.Read
return GetContextFromReader(ioutils.NewReadCloserWrapper(progReader, func() error { return response.Body.Close() }), dockerfileName)
}
// getWithStatusError does an http.Get() and returns an error if the
// status code is 4xx or 5xx.
func getWithStatusError(url string) (resp *http.Response, err error) {
if resp, err = http.Get(url); err != nil {
return nil, err
}
if resp.StatusCode < 400 {
return resp, nil
}
msg := fmt.Sprintf("failed to GET %s with status %s", url, resp.Status)
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, errors.Wrapf(err, msg+": error reading body")
}
return nil, errors.Errorf(msg+": %s", bytes.TrimSpace(body))
}
// GetContextFromLocalDir uses the given local directory as context for a
// `docker build`. Returns the absolute path to the local context directory,
// the relative path of the dockerfile in that context directory, and a non-nil
@ -376,27 +344,3 @@ func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCl
})
return buildCtx, randomName, nil
}
// Compress the build context for sending to the API
func Compress(buildCtx io.ReadCloser) (io.ReadCloser, error) {
pipeReader, pipeWriter := io.Pipe()
go func() {
compressWriter, err := archive.CompressStream(pipeWriter, archive.Gzip)
if err != nil {
pipeWriter.CloseWithError(err)
}
defer buildCtx.Close()
if _, err := pools.Copy(compressWriter, buildCtx); err != nil {
pipeWriter.CloseWithError(
errors.Wrap(err, "failed to compress context"))
compressWriter.Close()
return
}
compressWriter.Close()
pipeWriter.Close()
}()
return pipeReader, nil
}

View File

@ -266,35 +266,3 @@ func chdir(t *testing.T, dir string) func() {
require.NoError(t, os.Chdir(dir))
return func() { require.NoError(t, os.Chdir(workingDirectory)) }
}
func TestIsArchive(t *testing.T) {
var testcases = []struct {
doc string
header []byte
expected bool
}{
{
doc: "nil is not a valid header",
header: nil,
expected: false,
},
{
doc: "invalid header bytes",
header: []byte{0x00, 0x01, 0x02},
expected: false,
},
{
doc: "header for bzip2 archive",
header: []byte{0x42, 0x5A, 0x68},
expected: true,
},
{
doc: "header for 7zip archive is not supported",
header: []byte{0x50, 0x4b, 0x03, 0x04},
expected: false,
},
}
for _, testcase := range testcases {
assert.Equal(t, testcase.expected, IsArchive(testcase.header), testcase.doc)
}
}

View File

@ -1,151 +0,0 @@
package image
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sync"
"time"
"github.com/docker/cli/cli/command"
"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/pkg/errors"
"golang.org/x/time/rate"
)
const clientSessionRemote = "client-session"
func isSessionSupported(dockerCli command.Cli) bool {
return dockerCli.ServerInfo().HasExperimental && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31")
}
func trySession(dockerCli command.Cli, contextDir string) (*session.Session, error) {
var s *session.Session
if isSessionSupported(dockerCli) {
sharedKey, err := getBuildSharedKey(contextDir)
if err != nil {
return nil, errors.Wrap(err, "failed to get build shared key")
}
s, err = session.NewSession(filepath.Base(contextDir), sharedKey)
if err != nil {
return nil, errors.Wrap(err, "failed to create session")
}
}
return s, nil
}
func addDirToSession(session *session.Session, contextDir string, progressOutput progress.Output, done chan error) error {
excludes, err := build.ReadDockerignore(contextDir)
if err != nil {
return err
}
p := &sizeProgress{out: progressOutput, action: "Streaming build context to Docker daemon"}
workdirProvider := filesync.NewFSSyncProvider(contextDir, excludes)
session.Allow(workdirProvider)
// this will be replaced on parallel build jobs. keep the current
// progressbar for now
if snpc, ok := workdirProvider.(interface {
SetNextProgressCallback(func(int, bool), chan error)
}); ok {
snpc.SetNextProgressCallback(p.update, done)
}
return nil
}
type sizeProgress struct {
out progress.Output
action string
limiter *rate.Limiter
}
func (sp *sizeProgress) update(size int, last bool) {
if sp.limiter == nil {
sp.limiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 1)
}
if last || sp.limiter.Allow() {
sp.out.WriteProgress(progress.Progress{Action: sp.action, Current: int64(size), LastUpdate: last})
}
}
type bufferedWriter struct {
done chan error
io.Writer
buf *bytes.Buffer
flushed chan struct{}
mu sync.Mutex
}
func newBufferedWriter(done chan error, w io.Writer) *bufferedWriter {
bw := &bufferedWriter{done: done, Writer: w, buf: new(bytes.Buffer), flushed: make(chan struct{})}
go func() {
<-done
bw.flushBuffer()
}()
return bw
}
func (bw *bufferedWriter) Write(dt []byte) (int, error) {
select {
case <-bw.done:
bw.flushBuffer()
return bw.Writer.Write(dt)
default:
return bw.buf.Write(dt)
}
}
func (bw *bufferedWriter) flushBuffer() {
bw.mu.Lock()
select {
case <-bw.flushed:
default:
bw.Writer.Write(bw.buf.Bytes())
close(bw.flushed)
}
bw.mu.Unlock()
}
func getBuildSharedKey(dir string) (string, error) {
// build session is hash of build dir with node based randomness
s := sha256.Sum256([]byte(fmt.Sprintf("%s:%s", tryNodeIdentifier(), dir)))
return hex.EncodeToString(s[:]), nil
}
func tryNodeIdentifier() (out string) {
out = cliconfig.Dir() // return config dir as default on permission error
if err := os.MkdirAll(cliconfig.Dir(), 0700); err == nil {
sessionFile := filepath.Join(cliconfig.Dir(), ".buildNodeID")
if _, err := os.Lstat(sessionFile); err != nil {
if os.IsNotExist(err) { // create a new file with stored randomness
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return
}
if err := ioutil.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
return
}
}
}
dt, err := ioutil.ReadFile(sessionFile)
if err == nil {
return string(dt)
}
}
return
}

View File

@ -1,70 +0,0 @@
package image
import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
dest, err := ioutil.TempDir("", "test-build-compress-dest")
require.NoError(t, err)
defer os.RemoveAll(dest)
var dockerfileName string
fakeImageBuild := func(_ context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
buffer := new(bytes.Buffer)
tee := io.TeeReader(context, buffer)
assert.NoError(t, archive.Untar(tee, dest, nil))
dockerfileName = options.Dockerfile
header := buffer.Bytes()[:10]
assert.Equal(t, archive.Gzip, archive.DetectCompression(header))
body := new(bytes.Buffer)
return types.ImageBuildResponse{Body: ioutil.NopCloser(body)}, nil
}
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild})
dockerfile := bytes.NewBufferString(`
FROM alpine:3.6
COPY foo /
`)
cli.SetIn(command.NewInStream(ioutil.NopCloser(dockerfile)))
dir, err := ioutil.TempDir("", "test-build-compress")
require.NoError(t, err)
defer os.RemoveAll(dir)
ioutil.WriteFile(filepath.Join(dir, "foo"), []byte("some content"), 0644)
options := newBuildOptions()
options.compress = true
options.dockerfileName = "-"
options.context = dir
err = runBuild(cli, options)
require.NoError(t, err)
files, err := ioutil.ReadDir(dest)
require.NoError(t, err)
actual := []string{}
for _, fileInfo := range files {
actual = append(actual, fileInfo.Name())
}
sort.Strings(actual)
assert.Equal(t, []string{dockerfileName, ".dockerignore", "foo"}, actual)
}

View File

@ -27,7 +27,6 @@ type fakeClient struct {
imageInspectFunc func(image string) (types.ImageInspect, []byte, error)
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
imageHistoryFunc func(image string) ([]image.HistoryResponseItem, error)
imageBuildFunc func(context.Context, io.Reader, types.ImageBuildOptions) (types.ImageBuildResponse, error)
}
func (cli *fakeClient) ImageTag(_ context.Context, image, ref string) error {
@ -115,10 +114,3 @@ func (cli *fakeClient) ImageHistory(_ context.Context, img string) ([]image.Hist
}
return []image.HistoryResponseItem{{ID: img, Created: time.Now().Unix()}}, nil
}
func (cli *fakeClient) ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
if cli.imageBuildFunc != nil {
return cli.imageBuildFunc(ctx, context, options)
}
return types.ImageBuildResponse{Body: ioutil.NopCloser(strings.NewReader(""))}, nil
}

View File

@ -8,7 +8,8 @@ import (
)
// NewImageCommand returns a cobra command for `image` subcommands
func NewImageCommand(dockerCli command.Cli) *cobra.Command {
// nolint: interfacer
func NewImageCommand(dockerCli *command.DockerCli) *cobra.Command {
cmd := &cobra.Command{
Use: "image",
Short: "Manage images",

View File

@ -1,6 +1,7 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"regexp"
@ -37,7 +38,8 @@ func TestNewHistoryCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}))
buf := new(bytes.Buffer)
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -88,13 +90,13 @@ func TestNewHistoryCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc})
cmd := NewHistoryCommand(cli)
buf := new(bytes.Buffer)
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := cli.OutBuffer().String()
actual := buf.String()
if tc.outputRegex == "" {
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("history-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)

View File

@ -37,7 +37,7 @@ func TestNewImportCommandErrors(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewImportCommand(test.NewFakeCliWithOutput(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -45,7 +45,7 @@ func TestNewImportCommandErrors(t *testing.T) {
}
func TestNewImportCommandInvalidFile(t *testing.T) {
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{}))
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"testdata/import-command-success.unexistent-file"})
testutil.ErrorContains(t, cmd.Execute(), "testdata/import-command-success.unexistent-file")
@ -92,7 +92,7 @@ func TestNewImportCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewImportCommand(test.NewFakeCliWithOutput(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())

View File

@ -27,7 +27,7 @@ func TestNewInspectCommandErrors(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(test.NewFakeCliWithOutput(&fakeClient{}, buf))
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -79,7 +79,7 @@ func TestNewInspectCommandSuccess(t *testing.T) {
for _, tc := range testCases {
imageInspectInvocationCount = 0
buf := new(bytes.Buffer)
cmd := newInspectCommand(test.NewFakeCliWithOutput(&fakeClient{imageInspectFunc: tc.imageInspectFunc}, buf))
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{imageInspectFunc: tc.imageInspectFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()

View File

@ -36,7 +36,7 @@ func TestNewImagesCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}))
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -81,8 +81,8 @@ func TestNewImagesCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{imageListFunc: tc.imageListFunc}, buf)
cli.SetConfigFile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})
cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}, buf)
cli.SetConfigfile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})
cmd := NewImagesCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
@ -95,7 +95,7 @@ func TestNewImagesCommandSuccess(t *testing.T) {
}
func TestNewListCommandAlias(t *testing.T) {
cmd := newListCommand(test.NewFakeCli(&fakeClient{}))
cmd := newListCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
assert.True(t, cmd.HasAlias("images"))
assert.True(t, cmd.HasAlias("list"))
assert.False(t, cmd.HasAlias("other"))

View File

@ -43,7 +43,7 @@ func TestNewLoadCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc})
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, new(bytes.Buffer))
cli.In().SetIsTerminal(tc.isTerminalIn)
cmd := NewLoadCommand(cli)
cmd.SetOutput(ioutil.Discard)
@ -54,7 +54,7 @@ func TestNewLoadCommandErrors(t *testing.T) {
func TestNewLoadCommandInvalidInput(t *testing.T) {
expectedError := "open *"
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{}))
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"--input", "*"})
err := cmd.Execute()
@ -93,7 +93,7 @@ func TestNewLoadCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewLoadCommand(test.NewFakeCliWithOutput(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, buf))
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()

View File

@ -38,7 +38,7 @@ func TestNewPruneCommandErrors(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(test.NewFakeCliWithOutput(&fakeClient{
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
imagesPruneFunc: tc.imagesPruneFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)
@ -86,7 +86,7 @@ func TestNewPruneCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(test.NewFakeCliWithOutput(&fakeClient{
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
imagesPruneFunc: tc.imagesPruneFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)

View File

@ -1,21 +1,29 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/docker/docker/registry"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestNewPullCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
name string
args []string
expectedError string
trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named,
authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "wrong-args",
@ -39,8 +47,8 @@ func TestNewPullCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{})
cmd := NewPullCommand(cli)
buf := new(bytes.Buffer)
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -49,8 +57,10 @@ func TestNewPullCommandErrors(t *testing.T) {
func TestNewPullCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
name string
args []string
trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named,
authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "simple",
@ -62,13 +72,13 @@ func TestNewPullCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{})
cmd := NewPullCommand(cli)
buf := new(bytes.Buffer)
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := cli.OutBuffer().String()
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("pull-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}

View File

@ -1,16 +1,21 @@
package image
import (
"bytes"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestNewPushCommandErrors(t *testing.T) {
@ -45,8 +50,8 @@ func TestNewPushCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc})
cmd := NewPushCommand(cli)
buf := new(bytes.Buffer)
cmd := NewPushCommand(test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -55,8 +60,11 @@ func TestNewPushCommandErrors(t *testing.T) {
func TestNewPushCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
name string
args []string
trustedPushFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo,
ref reference.Named, authConfig types.AuthConfig,
requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "simple",
@ -64,12 +72,12 @@ func TestNewPushCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{
buf := new(bytes.Buffer)
cmd := NewPushCommand(test.NewFakeCli(&fakeClient{
imagePushFunc: func(ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
},
})
cmd := NewPushCommand(cli)
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())

View File

@ -56,8 +56,8 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error
}
var errs []string
for _, img := range images {
dels, err := client.ImageRemove(ctx, img, options)
for _, image := range images {
dels, err := client.ImageRemove(ctx, image, options)
if err != nil {
errs = append(errs, err.Error())
} else {
@ -72,11 +72,7 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error
}
if len(errs) > 0 {
msg := strings.Join(errs, "\n")
if !opts.force {
return errors.New(msg)
}
fmt.Fprintf(dockerCli.Err(), msg)
return errors.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

@ -1,6 +1,7 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
@ -14,7 +15,7 @@ import (
)
func TestNewRemoveCommandAlias(t *testing.T) {
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}))
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
assert.True(t, cmd.HasAlias("rmi"))
assert.True(t, cmd.HasAlias("remove"))
assert.False(t, cmd.HasAlias("other"))
@ -45,7 +46,7 @@ func TestNewRemoveCommandErrors(t *testing.T) {
for _, tc := range testCases {
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
imageRemoveFunc: tc.imageRemoveFunc,
}))
}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -57,7 +58,6 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
name string
args []string
imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
expectedErrMsg string
}{
{
name: "Image Deleted",
@ -67,15 +67,6 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
return []types.ImageDeleteResponseItem{{Deleted: image}}, nil
},
},
{
name: "Image Deleted with force option",
args: []string{"-f", "image1"},
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
assert.Equal(t, "image1", image)
return []types.ImageDeleteResponseItem{}, errors.Errorf("error removing image")
},
expectedErrMsg: "error removing image",
},
{
name: "Image Untagged",
args: []string{"image1"},
@ -96,15 +87,16 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
fakeCli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc})
cmd := NewRemoveCommand(fakeCli)
buf := new(bytes.Buffer)
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
imageRemoveFunc: tc.imageRemoveFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())
if tc.expectedErrMsg != "" {
assert.Equal(t, tc.expectedErrMsg, fakeCli.ErrBuffer().String())
}
actual := fakeCli.OutBuffer().String()
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("remove-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}

View File

@ -45,7 +45,7 @@ func TestNewSaveCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc})
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc}, new(bytes.Buffer))
cli.Out().SetIsTerminal(tc.isTerminal)
cmd := NewSaveCommand(cli)
cmd.SetOutput(ioutil.Discard)
@ -85,7 +85,7 @@ func TestNewSaveCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
cmd := NewSaveCommand(test.NewFakeCliWithOutput(&fakeClient{
cmd := NewSaveCommand(test.NewFakeCli(&fakeClient{
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
},

View File

@ -1,6 +1,7 @@
package image
import (
"bytes"
"io/ioutil"
"testing"
@ -16,8 +17,9 @@ func TestCliNewTagCommandErrors(t *testing.T) {
{"image1", "image2", "image3"},
}
expectedError := "\"tag\" requires exactly 2 argument(s)."
buf := new(bytes.Buffer)
for _, args := range testCases {
cmd := NewTagCommand(test.NewFakeCli(&fakeClient{}))
cmd := NewTagCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetArgs(args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), expectedError)
@ -25,6 +27,7 @@ func TestCliNewTagCommandErrors(t *testing.T) {
}
func TestCliNewTagCommand(t *testing.T) {
buf := new(bytes.Buffer)
cmd := NewTagCommand(
test.NewFakeCli(&fakeClient{
imageTagFunc: func(image string, ref string) error {
@ -32,7 +35,7 @@ func TestCliNewTagCommand(t *testing.T) {
assert.Equal(t, "image2", ref)
return nil
},
}))
}, buf))
cmd.SetArgs([]string{"image1", "image2"})
cmd.SetOutput(ioutil.Discard)
assert.NoError(t, cmd.Execute())

View File

@ -21,9 +21,6 @@
},
"RootFS": {
"Type": ""
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
},
{
@ -48,9 +45,6 @@
},
"RootFS": {
"Type": ""
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]

View File

@ -21,9 +21,6 @@
},
"RootFS": {
"Type": ""
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]

View File

@ -1,2 +1,4 @@
Untagged: image1
Deleted: image2
Untagged: image1
Deleted: image2

View File

@ -1 +1,2 @@
Deleted: image1
Deleted: image1

View File

@ -1 +1,2 @@
Untagged: image1
Untagged: image1

View File

@ -110,7 +110,6 @@ func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte) error {
buffer := new(bytes.Buffer)
rdr := bytes.NewReader(rawElement)
dec := json.NewDecoder(rdr)
dec.UseNumber()
if rawErr := dec.Decode(&raw); rawErr != nil {
return errors.Errorf("unable to read inspect data: %v", rawErr)

View File

@ -6,8 +6,6 @@ import (
"testing"
"github.com/docker/docker/pkg/templates"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testElement struct {
@ -221,39 +219,3 @@ func TestIndentedInspectorRawElements(t *testing.T) {
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
}
}
// moby/moby#32235
// This test verifies that even if `tryRawInspectFallback` is called the fields containing
// numerical values are displayed correctly.
// For example, `docker inspect --format "{{.Id}} {{.Size}} alpine` and
// `docker inspect --format "{{.ID}} {{.Size}} alpine" will have the same output which is
// sha256:651aa95985aa4a17a38ffcf71f598ec461924ca96865facc2c5782ef2d2be07f 3983636
func TestTemplateInspectorRawFallbackNumber(t *testing.T) {
// Using typedElem to automatically fall to tryRawInspectFallback.
typedElem := struct {
ID string `json:"Id"`
}{"ad3"}
testcases := []struct {
raw []byte
exp string
}{
{raw: []byte(`{"Id": "ad3", "Size": 53317}`), exp: "53317 ad3\n"},
{raw: []byte(`{"Id": "ad3", "Size": 53317.102}`), exp: "53317.102 ad3\n"},
{raw: []byte(`{"Id": "ad3", "Size": 53317.0}`), exp: "53317.0 ad3\n"},
}
b := new(bytes.Buffer)
tmpl, err := templates.Parse("{{.Size}} {{.Id}}")
require.NoError(t, err)
i := NewTemplateInspector(b, tmpl)
for _, tc := range testcases {
err = i.Inspect(typedElem, tc.raw)
require.NoError(t, err)
err = i.Flush()
require.NoError(t, err)
assert.Equal(t, tc.exp, b.String())
b.Reset()
}
}

View File

@ -1,19 +0,0 @@
package network
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
}
func (c *fakeClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
if c.networkCreateFunc != nil {
return c.networkCreateFunc(ctx, name, options)
}
return types.NetworkCreateResponse{}, nil
}

View File

@ -8,7 +8,8 @@ import (
)
// NewNetworkCommand returns a cobra command for `network` subcommands
func NewNetworkCommand(dockerCli command.Cli) *cobra.Command {
// nolint: interfacer
func NewNetworkCommand(dockerCli *command.DockerCli) *cobra.Command {
cmd := &cobra.Command{
Use: "network",
Short: "Manage networks",

View File

@ -19,7 +19,7 @@ type connectOptions struct {
linklocalips []string
}
func newConnectCommand(dockerCli command.Cli) *cobra.Command {
func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
options := connectOptions{
links: opts.NewListOpts(opts.ValidateLink),
}
@ -45,7 +45,7 @@ func newConnectCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runConnect(dockerCli command.Cli, options connectOptions) error {
func runConnect(dockerCli *command.DockerCli, options connectOptions) error {
client := dockerCli.Client()
epConfig := &network.EndpointSettings{

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -36,7 +37,7 @@ type createOptions struct {
ipamOpt opts.MapOpts
}
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
options := createOptions{
driverOpts: *opts.NewMapOpts(nil, nil),
labels: opts.NewListOpts(opts.ValidateEnv),
@ -82,7 +83,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runCreate(dockerCli command.Cli, options createOptions) error {
func runCreate(dockerCli *command.DockerCli, options createOptions) error {
client := dockerCli.Client()
ipamCfg, err := consolidateIpam(options.ipamSubnet, options.ipamIPRange, options.ipamGateway, options.ipamAux.GetAll())
@ -106,7 +107,7 @@ func runCreate(dockerCli command.Cli, options createOptions) error {
Ingress: options.ingress,
Scope: options.scope,
ConfigOnly: options.configOnly,
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
}
if from := options.configFrom; from != "" {
@ -232,13 +233,13 @@ func subnetMatches(subnet, data string) (bool, error) {
_, s, err := net.ParseCIDR(subnet)
if err != nil {
return false, errors.Wrap(err, "invalid subnet")
return false, errors.Errorf("Invalid subnet %s : %v", s, err)
}
if strings.Contains(data, "/") {
ip, _, err = net.ParseCIDR(data)
if err != nil {
return false, err
return false, errors.Errorf("Invalid cidr %s : %v", data, err)
}
} else {
ip = net.ParseIP(data)

View File

@ -1,175 +0,0 @@
package network
import (
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestNetworkCreateErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
expectedError string
}{
{
expectedError: "exactly 1 argument",
},
{
args: []string{"toto"},
networkCreateFunc: func(ctx context.Context, name string, createBody types.NetworkCreate) (types.NetworkCreateResponse, error) {
return types.NetworkCreateResponse{}, errors.Errorf("error creating network")
},
expectedError: "error creating network",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.0.0/24",
"gateway": "255.0.255.0/24",
"subnet": "10.1.2.0.30.50",
},
expectedError: "invalid CIDR address: 10.1.2.0.30.50",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.0.0.30/24",
"gateway": "255.0.255.0/24",
"subnet": "255.0.0.0/24",
},
expectedError: "invalid CIDR address: 255.255.0.0.30/24",
},
{
args: []string{"toto"},
flags: map[string]string{
"gateway": "255.0.0.0/24",
},
expectedError: "every ip-range or gateway must have a corresponding subnet",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.0.0.0/24",
},
expectedError: "every ip-range or gateway must have a corresponding subnet",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.0.0.0/24",
"gateway": "255.0.0.0/24",
},
expectedError: "every ip-range or gateway must have a corresponding subnet",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.0.0/24",
"gateway": "255.0.255.0/24",
"subnet": "10.1.2.0/23,10.1.3.248/30",
},
expectedError: "multiple overlapping subnet configuration is not supported",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "192.168.1.0/24,192.168.1.200/24",
"gateway": "192.168.1.1,192.168.1.4",
"subnet": "192.168.2.0/24,192.168.1.250/24",
},
expectedError: "cannot configure multiple ranges (192.168.1.200/24, 192.168.1.0/24) on the same subnet (192.168.1.250/24)",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.200.0/24,255.255.120.0/24",
"gateway": "255.0.255.0/24",
"subnet": "255.255.255.0/24,255.255.0.255/24",
},
expectedError: "no matching subnet for range 255.255.200.0/24",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "192.168.1.0/24",
"gateway": "192.168.1.1,192.168.1.4",
"subnet": "192.168.2.0/24,192.168.1.250/24",
},
expectedError: "cannot configure multiple gateways (192.168.1.4, 192.168.1.1) for the same subnet (192.168.1.250/24)",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "192.168.1.0/24",
"gateway": "192.168.4.1,192.168.5.4",
"subnet": "192.168.2.0/24,192.168.1.250/24",
},
expectedError: "no matching subnet for gateway 192.168.4.1",
},
{
args: []string{"toto"},
flags: map[string]string{
"gateway": "255.255.0.0/24",
"subnet": "255.255.0.0/24",
"aux-address": "255.255.0.30/24",
},
expectedError: "no matching subnet for aux-address",
},
}
for _, tc := range testCases {
cmd := newCreateCommand(
test.NewFakeCli(&fakeClient{
networkCreateFunc: tc.networkCreateFunc,
}),
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
require.NoError(t, cmd.Flags().Set(key, value))
}
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNetworkCreateWithFlags(t *testing.T) {
expectedDriver := "foo"
expectedOpts := []network.IPAMConfig{
{
"192.168.4.0/24",
"192.168.4.0/24",
"192.168.4.1/24",
map[string]string{},
},
}
cli := test.NewFakeCli(&fakeClient{
networkCreateFunc: func(ctx context.Context, name string, createBody types.NetworkCreate) (types.NetworkCreateResponse, error) {
assert.Equal(t, expectedDriver, createBody.Driver, "not expected driver error")
assert.Equal(t, expectedOpts, createBody.IPAM.Config, "not expected driver error")
return types.NetworkCreateResponse{
ID: name,
}, nil
},
})
args := []string{"banana"}
cmd := newCreateCommand(cli)
cmd.SetArgs(args)
cmd.Flags().Set("driver", "foo")
cmd.Flags().Set("ip-range", "192.168.4.0/24")
cmd.Flags().Set("gateway", "192.168.4.1/24")
cmd.Flags().Set("subnet", "192.168.4.0/24")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "banana", strings.TrimSpace(cli.OutBuffer().String()))
}

View File

@ -14,7 +14,7 @@ type disconnectOptions struct {
force bool
}
func newDisconnectCommand(dockerCli command.Cli) *cobra.Command {
func newDisconnectCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := disconnectOptions{}
cmd := &cobra.Command{
@ -34,7 +34,7 @@ func newDisconnectCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runDisconnect(dockerCli command.Cli, opts disconnectOptions) error {
func runDisconnect(dockerCli *command.DockerCli, opts disconnectOptions) error {
client := dockerCli.Client()
return client.NetworkDisconnect(context.Background(), opts.network, opts.container, opts.force)

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/inspect"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)
@ -16,7 +15,7 @@ type inspectOptions struct {
verbose bool
}
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts inspectOptions
cmd := &cobra.Command{
@ -35,13 +34,13 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
client := dockerCli.Client()
ctx := context.Background()
getNetFunc := func(name string) (interface{}, []byte, error) {
return client.NetworkInspectWithRaw(ctx, name, types.NetworkInspectOptions{Verbose: opts.verbose})
return client.NetworkInspectWithRaw(ctx, name, opts.verbose)
}
return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc)

View File

@ -25,7 +25,7 @@ type listOptions struct {
filter opts.FilterOpt
}
func newListCommand(dockerCli command.Cli) *cobra.Command {
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
options := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
@ -47,7 +47,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runList(dockerCli command.Cli, options listOptions) error {
func runList(dockerCli *command.DockerCli, options listOptions) error {
client := dockerCli.Client()
listOptions := types.NetworkListOptions{Filters: options.filter.Value()}
networkResources, err := client.NetworkList(context.Background(), listOptions)

View File

@ -7,11 +7,10 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
return &cobra.Command{
Use: "rm NETWORK [NETWORK...]",
Aliases: []string{"remove"},
@ -28,13 +27,13 @@ const ingressWarning = "WARNING! Before removing the routing-mesh network, " +
"Otherwise, removal may not be effective and functionality of newly create " +
"ingress networks will be impaired.\nAre you sure you want to continue?"
func runRemove(dockerCli command.Cli, networks []string) error {
func runRemove(dockerCli *command.DockerCli, networks []string) error {
client := dockerCli.Client()
ctx := context.Background()
status := 0
for _, name := range networks {
if nw, _, err := client.NetworkInspectWithRaw(ctx, name, types.NetworkInspectOptions{}); err == nil &&
if nw, _, err := client.NetworkInspectWithRaw(ctx, name, false); err == nil &&
nw.Ingress &&
!command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), ingressWarning) {
continue

View File

@ -1,6 +1,7 @@
package node
import (
"bytes"
"io/ioutil"
"testing"
@ -39,11 +40,12 @@ func TestNodeDemoteErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newDemoteCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}))
}, buf))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -51,6 +53,7 @@ func TestNodeDemoteErrors(t *testing.T) {
}
func TestNodeDemoteNoChange(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newDemoteCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
@ -62,12 +65,13 @@ func TestNodeDemoteNoChange(t *testing.T) {
}
return nil
},
}))
}, buf))
cmd.SetArgs([]string{"nodeID"})
assert.NoError(t, cmd.Execute())
}
func TestNodeDemoteMultipleNode(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newDemoteCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
@ -79,7 +83,7 @@ func TestNodeDemoteMultipleNode(t *testing.T) {
}
return nil
},
}))
}, buf))
cmd.SetArgs([]string{"nodeID1", "nodeID2"})
assert.NoError(t, cmd.Execute())
}

View File

@ -69,7 +69,7 @@ func TestNodeInspectErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
infoFunc: tc.infoFunc,
}, buf))
@ -111,7 +111,7 @@ func TestNodeInspectPretty(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
}, buf))
cmd.SetArgs([]string{"nodeID"})

View File

@ -1,27 +1,15 @@
package node
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 byHostname []swarm.Node
func (n byHostname) Len() int { return len(n) }
func (n byHostname) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n byHostname) Less(i, j int) bool {
return sortorder.NaturalLess(n[i].Description.Hostname, n[j].Description.Hostname)
}
type listOptions struct {
quiet bool
format string
@ -80,6 +68,5 @@ func runList(dockerCli command.Cli, options listOptions) error {
Output: dockerCli.Out(),
Format: formatter.NewNodeFormat(format, options.quiet),
}
sort.Sort(byHostname(nodes))
return formatter.NodeWrite(nodesCtx, nodes, info)
}

View File

@ -9,8 +9,6 @@ import (
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
// Import builders to get the builder function as package function
. "github.com/docker/cli/cli/internal/test/builders"
@ -44,10 +42,12 @@ func TestNodeListErrorOnAPIFailure(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: tc.nodeListFunc,
infoFunc: tc.infoFunc,
})
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
cmd.SetOutput(ioutil.Discard)
assert.EqualError(t, cmd.Execute(), tc.expectedError)
@ -55,6 +55,7 @@ func TestNodeListErrorOnAPIFailure(t *testing.T) {
}
func TestNodeList(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
@ -70,25 +71,25 @@ func TestNodeList(t *testing.T) {
},
}, nil
},
})
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
assert.NoError(t, cmd.Execute())
out := cli.OutBuffer().String()
assert.Contains(t, out, `nodeID1 * nodeHostname1 Ready Active Leader`)
assert.Contains(t, out, `nodeID2 nodeHostname2 Ready Active Reachable`)
assert.Contains(t, out, `nodeID3 nodeHostname3 Ready Active`)
assert.Contains(t, buf.String(), `nodeID1 * nodeHostname1 Ready Active Leader`)
assert.Contains(t, buf.String(), `nodeID2 nodeHostname2 Ready Active Reachable`)
assert.Contains(t, buf.String(), `nodeID3 nodeHostname3 Ready Active`)
}
func TestNodeListQuietShouldOnlyPrintIDs(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
*Node(),
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
cmd.Flags().Set("quiet", "true")
assert.NoError(t, cmd.Execute())
@ -98,7 +99,8 @@ func TestNodeListQuietShouldOnlyPrintIDs(t *testing.T) {
// Test case for #24090
func TestNodeListContainsHostname(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{}, buf)
cli := test.NewFakeCli(&fakeClient{}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
assert.NoError(t, cmd.Execute())
assert.Contains(t, buf.String(), "HOSTNAME")
@ -106,7 +108,7 @@ func TestNodeListContainsHostname(t *testing.T) {
func TestNodeListDefaultFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
*Node(NodeID("nodeID1"), Hostname("nodeHostname1"), Manager(Leader())),
@ -122,7 +124,7 @@ func TestNodeListDefaultFormat(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigFile(&configfile.ConfigFile{
cli.SetConfigfile(&configfile.ConfigFile{
NodesFormat: "{{.ID}}: {{.Hostname}} {{.Status}}/{{.ManagerStatus}}",
})
cmd := newListCommand(cli)
@ -134,7 +136,7 @@ func TestNodeListDefaultFormat(t *testing.T) {
func TestNodeListFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
*Node(NodeID("nodeID1"), Hostname("nodeHostname1"), Manager(Leader())),
@ -149,7 +151,7 @@ func TestNodeListFormat(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigFile(&configfile.ConfigFile{
cli.SetConfigfile(&configfile.ConfigFile{
NodesFormat: "{{.ID}}: {{.Hostname}} {{.Status}}/{{.ManagerStatus}}",
})
cmd := newListCommand(cli)
@ -158,22 +160,3 @@ func TestNodeListFormat(t *testing.T) {
assert.Contains(t, buf.String(), `nodeHostname1: Leader`)
assert.Contains(t, buf.String(), `nodeHostname2: Reachable`)
}
func TestNodeListOrder(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
*Node(Hostname("node-2-foo"), Manager(Leader())),
*Node(Hostname("node-10-foo"), Manager()),
*Node(Hostname("node-1-foo")),
}, nil
},
})
cmd := newListCommand(cli)
cmd.Flags().Set("format", "{{.Hostname}}: {{.ManagerStatus}}")
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
expected := golden.Get(t, []byte(actual), "node-list-sort.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -11,6 +11,7 @@ type nodeOptions struct {
}
type annotations struct {
name string
labels opts.ListOpts
}

View File

@ -42,7 +42,7 @@ func TestNodePromoteErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newPromoteCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}, buf))
@ -55,7 +55,7 @@ func TestNodePromoteErrors(t *testing.T) {
func TestNodePromoteNoChange(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newPromoteCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
return *Node(Manager()), []byte{}, nil
},
@ -73,7 +73,7 @@ func TestNodePromoteNoChange(t *testing.T) {
func TestNodePromoteMultipleNode(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newPromoteCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
return *Node(), []byte{}, nil
},

View File

@ -5,6 +5,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/task"
"github.com/docker/cli/opts"
@ -87,7 +88,11 @@ func runPs(dockerCli command.Cli, options psOptions) error {
format := options.format
if len(format) == 0 {
format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet)
if dockerCli.ConfigFile() != nil && len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().TasksFormat
} else {
format = formatter.TableFormatKey
}
}
if len(errs) == 0 || len(tasks) != 0 {

View File

@ -52,7 +52,7 @@ func TestNodePsErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newPsCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
infoFunc: tc.infoFunc,
nodeInspectFunc: tc.nodeInspectFunc,
taskInspectFunc: tc.taskInspectFunc,
@ -103,11 +103,11 @@ func TestNodePs(t *testing.T) {
},
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{
*Task(TaskID("taskID1"), TaskServiceID("failure"),
*Task(TaskID("taskID1"), ServiceID("failure"),
WithStatus(Timestamp(time.Now().Add(-2*time.Hour)), StatusErr("a task error"))),
*Task(TaskID("taskID2"), TaskServiceID("failure"),
*Task(TaskID("taskID2"), ServiceID("failure"),
WithStatus(Timestamp(time.Now().Add(-3*time.Hour)), StatusErr("a task error"))),
*Task(TaskID("taskID3"), TaskServiceID("failure"),
*Task(TaskID("taskID3"), ServiceID("failure"),
WithStatus(Timestamp(time.Now().Add(-4*time.Hour)), StatusErr("a task error"))),
}, nil
},
@ -116,7 +116,7 @@ func TestNodePs(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newPsCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
infoFunc: tc.infoFunc,
nodeInspectFunc: tc.nodeInspectFunc,
taskInspectFunc: tc.taskInspectFunc,

View File

@ -31,7 +31,7 @@ func TestNodeRemoveErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newRemoveCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeRemoveFunc: tc.nodeRemoveFunc,
}, buf))
cmd.SetArgs(tc.args)
@ -42,7 +42,7 @@ func TestNodeRemoveErrors(t *testing.T) {
func TestNodeRemoveMultiple(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newRemoveCommand(test.NewFakeCliWithOutput(&fakeClient{}, buf))
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetArgs([]string{"nodeID1", "nodeID2"})
assert.NoError(t, cmd.Execute())
}

View File

@ -1,3 +0,0 @@
node-1-foo:
node-2-foo: Leader
node-10-foo: Reachable

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/swarm"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -94,7 +95,7 @@ func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) error {
}
if flags.Changed(flagLabelAdd) {
labels := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll()
for k, v := range opts.ConvertKVStringsToMap(labels) {
for k, v := range runconfigopts.ConvertKVStringsToMap(labels) {
spec.Annotations.Labels[k] = v
}
}

View File

@ -1,6 +1,7 @@
package node
import (
"bytes"
"io/ioutil"
"testing"
@ -56,11 +57,12 @@ func TestNodeUpdateErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newUpdateCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}))
}, buf))
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
@ -156,11 +158,12 @@ func TestNodeUpdate(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newUpdateCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}))
}, buf))
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)

View File

@ -0,0 +1,51 @@
package prune
import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/container"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/command/network"
"github.com/docker/cli/cli/command/volume"
"github.com/docker/cli/opts"
"github.com/spf13/cobra"
)
// NewContainerPruneCommand returns a cobra prune command for containers
func NewContainerPruneCommand(dockerCli command.Cli) *cobra.Command {
return container.NewPruneCommand(dockerCli)
}
// NewVolumePruneCommand returns a cobra prune command for volumes
func NewVolumePruneCommand(dockerCli command.Cli) *cobra.Command {
return volume.NewPruneCommand(dockerCli)
}
// NewImagePruneCommand returns a cobra prune command for images
func NewImagePruneCommand(dockerCli command.Cli) *cobra.Command {
return image.NewPruneCommand(dockerCli)
}
// NewNetworkPruneCommand returns a cobra prune command for Networks
func NewNetworkPruneCommand(dockerCli command.Cli) *cobra.Command {
return network.NewPruneCommand(dockerCli)
}
// RunContainerPrune executes a prune command for containers
func RunContainerPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
return container.RunPrune(dockerCli, filter)
}
// RunVolumePrune executes a prune command for volumes
func RunVolumePrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
return volume.RunPrune(dockerCli, filter)
}
// RunImagePrune executes a prune command for images
func RunImagePrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
return image.RunPrune(dockerCli, all, filter)
}
// RunNetworkPrune executes a prune command for networks
func RunNetworkPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
return network.RunPrune(dockerCli, filter)
}

View File

@ -28,9 +28,7 @@ func ElectAuthServer(ctx context.Context, cli Cli) string {
// the default registry URL might be Windows specific.
serverAddress := registry.IndexServer
if info, err := cli.Client().Info(ctx); err != nil {
fmt.Fprintf(cli.Err(), "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress)
} else if info.IndexServerAddress == "" {
fmt.Fprintf(cli.Err(), "Warning: Empty registry endpoint from daemon. Using system default: %s\n", serverAddress)
fmt.Fprintf(cli.Out(), "Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s\n", err, serverAddress)
} else {
serverAddress = info.IndexServerAddress
}
@ -70,7 +68,7 @@ func ResolveAuthConfig(ctx context.Context, cli Cli, index *registrytypes.IndexI
configKey = ElectAuthServer(ctx, cli)
}
a, _ := cli.ConfigFile().GetAuthConfig(configKey)
a, _ := cli.CredentialsStore(configKey).Get(configKey)
return a
}
@ -85,7 +83,7 @@ func ConfigureAuth(cli Cli, flUser, flPassword, serverAddress string, isDefaultR
serverAddress = registry.ConvertToHostname(serverAddress)
}
authconfig, err := cli.ConfigFile().GetAuthConfig(serverAddress)
authconfig, err := cli.CredentialsStore(serverAddress).Get(serverAddress)
if err != nil {
return authconfig, err
}

View File

@ -2,8 +2,6 @@ package registry
import (
"fmt"
"io/ioutil"
"strings"
"golang.org/x/net/context"
@ -18,7 +16,7 @@ type loginOptions struct {
serverAddress string
user string
password string
passwordStdin bool
email string
}
// NewLoginCommand creates a new `docker login` command
@ -42,7 +40,10 @@ func NewLoginCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVarP(&opts.user, "username", "u", "", "Username")
flags.StringVarP(&opts.password, "password", "p", "", "Password")
flags.BoolVarP(&opts.passwordStdin, "password-stdin", "", false, "Take the password from stdin")
// Deprecated in 1.11: Should be removed in docker 17.06
flags.StringVarP(&opts.email, "email", "e", "", "Email")
flags.MarkDeprecated("email", "will be removed in 17.06.")
return cmd
}
@ -51,27 +52,6 @@ func runLogin(dockerCli command.Cli, opts loginOptions) error {
ctx := context.Background()
clnt := dockerCli.Client()
if opts.password != "" {
fmt.Fprintln(dockerCli.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
if opts.passwordStdin {
return errors.New("--password and --password-stdin are mutually exclusive")
}
}
if opts.passwordStdin {
if opts.user == "" {
return errors.New("Must provide --username with --password-stdin")
}
contents, err := ioutil.ReadAll(dockerCli.In())
if err != nil {
return err
}
opts.password = strings.TrimSuffix(string(contents), "\n")
opts.password = strings.TrimSuffix(opts.password, "\r")
}
var (
serverAddress string
authServer = command.ElectAuthServer(ctx, dockerCli)
@ -96,7 +76,7 @@ func runLogin(dockerCli command.Cli, opts loginOptions) error {
authConfig.Password = ""
authConfig.IdentityToken = response.IdentityToken
}
if err := dockerCli.ConfigFile().GetCredentialsStore(serverAddress).Store(authConfig); err != nil {
if err := dockerCli.CredentialsStore(serverAddress).Store(authConfig); err != nil {
return errors.Errorf("Error saving credentials: %v", err)
}

View File

@ -68,7 +68,7 @@ func runLogout(dockerCli command.Cli, serverAddress string) error {
fmt.Fprintf(dockerCli.Out(), "Removing login credentials for %s\n", hostnameAddress)
for _, r := range regsToLogout {
if err := dockerCli.ConfigFile().GetCredentialsStore(r).Erase(r); err != nil {
if err := dockerCli.CredentialsStore(r).Erase(r); err != nil {
fmt.Fprintf(dockerCli.Err(), "WARNING: could not erase credentials: %v\n", err)
}
}

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