diff --git a/components/cli/Jenkinsfile b/components/cli/Jenkinsfile new file mode 100644 index 0000000000..c5fd505597 --- /dev/null +++ b/components/cli/Jenkinsfile @@ -0,0 +1,12 @@ +wrappedNode(label: 'linux && x86_64', cleanWorkspace: true) { + timeout(time: 60, unit: 'MINUTES') { + stage "Git Checkout" + checkout scm + + stage "Run end-to-end test suite" + sh "docker version" + sh "E2E_UNIQUE_ID=clie2e${BUILD_NUMBER} \ + IMAGE_TAG=clie2e${BUILD_NUMBER} \ + make -f docker.Makefile test-e2e" + } +} diff --git a/components/cli/Makefile b/components/cli/Makefile index 771f496abe..17ba8e5b2d 100644 --- a/components/cli/Makefile +++ b/components/cli/Makefile @@ -4,17 +4,19 @@ all: binary +_:=$(shell ./scripts/warn-outside-container $(MAKECMDGOALS)) + .PHONY: clean clean: ## remove build artifacts rm -rf ./build/* cli/winresources/rsrc_* ./man/man[1-9] docs/yaml/gen -.PHONY: test -test: ## run go test - ./scripts/test/unit $(shell go list ./... | grep -v '/vendor/') +.PHONY: test-unit +test-unit: ## run unit test + ./scripts/test/unit $(shell go list ./... | grep -vE '/vendor/|/e2e/') .PHONY: test-coverage test-coverage: ## run test coverage - ./scripts/test/unit-with-coverage $(shell go list ./... | grep -v '/vendor/') + ./scripts/test/unit-with-coverage $(shell go list ./... | grep -vE '/vendor/|/e2e/') .PHONY: lint lint: ## run all the lint tools @@ -63,3 +65,10 @@ cli/compose/schema/bindata.go: cli/compose/schema/data/*.json compose-jsonschema: cli/compose/schema/bindata.go scripts/validate/check-git-diff cli/compose/schema/bindata.go + +.PHONY: ci-validate +ci-validate: + time make -B vendor + time make -B compose-jsonschema + time make manpages + time make yamldocs diff --git a/components/cli/TESTING.md b/components/cli/TESTING.md new file mode 100644 index 0000000000..63bba1d3dc --- /dev/null +++ b/components/cli/TESTING.md @@ -0,0 +1,87 @@ +# Testing + +The following guidelines summarize the testing policy for docker/cli. + +## Unit Test Suite + +All code changes should have unit test coverage. + +Error cases should be tested with unit tests. + +Bug fixes should be covered by new unit tests or additional assertions in +existing unit tests. + +### Details + +The unit test suite follows the standard Go testing convention. Tests are +located in the package directory in `_test.go` files. + +Unit tests should be named using the convention: + +``` +Test +``` + +[Table tests](https://github.com/golang/go/wiki/TableDrivenTests) should be used +where appropriate, but may not be appropriate in all cases. + +Assertions should be made using +[testify/assert](https://godoc.org/github.com/stretchr/testify/assert) and test +requirements should be verified using +[testify/require](https://godoc.org/github.com/stretchr/testify/require). + +Fakes, and testing utilities can be found in +[internal/test](https://godoc.org/github.com/docker/cli/internal/test) and +[gotestyourself](https://godoc.org/github.com/gotestyourself/gotestyourself). + +## End-to-End Test Suite + +The end-to-end test suite tests a cli binary against a real API backend. + +### Guidelines + +Each feature (subcommand) should have a single end-to-end test for +the success case. The test should include all (or most) flags/options supported +by that feature. + +In some rare cases a couple additional end-to-end tests may be written for a +sufficiently complex and critical feature (ex: `container run`, `service +create`, `service update`, and `docker build` may have ~3-5 cases each). + +In some rare cases a sufficiently critical error paths may have a single +end-to-end test case. + +In all other cases the behaviour should be covered by unit tests. + +If a code change adds a new flag, that flag should be added to the existing +"success case" end-to-end test. + +If a code change fixes a bug, that bug fix should be covered either by adding +assertions to the existing end-to-end test, or with one or more unit test. + +### Details + +The end-to-end test suite is located in +[./e2e](https://github.com/docker/cli/tree/master/e2e). Each directory in `e2e` +corresponds to a directory in `cli/command` and contains the tests for that +subcommand. Files in each directory should be named `_test.go` where +command is the basename of the command (ex: the test for `docker stack deploy` +is found in `e2e/stack/deploy_test.go`). + +Tests should be named using the convention: + +``` +Test[] +``` + +where the test case name is only required when there are multiple test cases for +a single command. + +End-to-end test should run the `docker` binary using +[gotestyourself/icmd](https://godoc.org/github.com/gotestyourself/gotestyourself/icmd) +and make assertions about the exit code, stdout, stderr, and local file system. + +Any Docker image or registry operations should use `registry:5000/` +to communicate with the local instance of the Docker registry. To load +additional fixture images to the registry see +[scripts/test/e2e/run](https://github.com/docker/cli/blob/master/scripts/test/e2e/run). diff --git a/components/cli/circle.yml b/components/cli/circle.yml index 2a9c196750..53fcac7389 100644 --- a/components/cli/circle.yml +++ b/components/cli/circle.yml @@ -71,7 +71,8 @@ jobs: 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 + curl -s https://codecov.io/bash | bash || \ + echo 'Codecov failed to upload' validate: working_directory: /work @@ -89,7 +90,7 @@ jobs: 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 + make ci-validate shellcheck: working_directory: /work docker: [{image: 'docker:17.06-git'}] @@ -103,7 +104,7 @@ jobs: echo "COPY . ." >> $dockerfile docker build -f $dockerfile --tag cli-validator:$CIRCLE_BUILD_NUM . docker run --rm cli-validator:$CIRCLE_BUILD_NUM \ - make -B shellcheck + make shellcheck workflows: version: 2 ci: diff --git a/components/cli/cli/command/checkpoint/create_test.go b/components/cli/cli/command/checkpoint/create_test.go index 32c9d88a6f..19bd3920c0 100644 --- a/components/cli/cli/command/checkpoint/create_test.go +++ b/components/cli/cli/command/checkpoint/create_test.go @@ -5,9 +5,9 @@ import ( "strings" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) diff --git a/components/cli/cli/command/checkpoint/list_test.go b/components/cli/cli/command/checkpoint/list_test.go index 1c13f27755..373460d3be 100644 --- a/components/cli/cli/command/checkpoint/list_test.go +++ b/components/cli/cli/command/checkpoint/list_test.go @@ -1,14 +1,13 @@ package checkpoint import ( - "bytes" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -37,9 +36,9 @@ func TestCheckpointListErrors(t *testing.T) { } for _, tc := range testCases { - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ checkpointListFunc: tc.checkpointListFunc, - }, &bytes.Buffer{}) + }) cmd := newListCommand(cli) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) @@ -49,8 +48,7 @@ func TestCheckpointListErrors(t *testing.T) { func TestCheckpointListWithOptions(t *testing.T) { var containerID, checkpointDir string - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ checkpointListFunc: func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) { containerID = container checkpointDir = options.CheckpointDir @@ -58,14 +56,12 @@ func TestCheckpointListWithOptions(t *testing.T) { {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)) + golden.Assert(t, cli.OutBuffer().String(), "checkpoint-list-with-options.golden") } diff --git a/components/cli/cli/command/checkpoint/remove_test.go b/components/cli/cli/command/checkpoint/remove_test.go index 3d85d16cda..6100d75e41 100644 --- a/components/cli/cli/command/checkpoint/remove_test.go +++ b/components/cli/cli/command/checkpoint/remove_test.go @@ -1,13 +1,12 @@ package checkpoint import ( - "bytes" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -36,9 +35,9 @@ func TestCheckpointRemoveErrors(t *testing.T) { } for _, tc := range testCases { - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ checkpointDeleteFunc: tc.checkpointDeleteFunc, - }, &bytes.Buffer{}) + }) cmd := newRemoveCommand(cli) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) @@ -48,14 +47,14 @@ func TestCheckpointRemoveErrors(t *testing.T) { func TestCheckpointRemoveWithOptions(t *testing.T) { var containerID, checkpointID, checkpointDir string - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&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") diff --git a/components/cli/cli/command/cli_test.go b/components/cli/cli/command/cli_test.go new file mode 100644 index 0000000000..ca73a05e38 --- /dev/null +++ b/components/cli/cli/command/cli_test.go @@ -0,0 +1,57 @@ +package command + +import ( + "os" + "testing" + + "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/cli/flags" + "github.com/docker/docker/api" + "github.com/docker/docker/client" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNewAPIClientFromFlags(t *testing.T) { + host := "unix://path" + opts := &flags.CommonOptions{Hosts: []string{host}} + configFile := &configfile.ConfigFile{ + HTTPHeaders: map[string]string{ + "My-Header": "Custom-Value", + }, + } + apiclient, err := NewAPIClientFromFlags(opts, configFile) + require.NoError(t, err) + assert.Equal(t, host, apiclient.DaemonHost()) + + expectedHeaders := map[string]string{ + "My-Header": "Custom-Value", + "User-Agent": UserAgent(), + } + assert.Equal(t, expectedHeaders, apiclient.(*client.Client).CustomHTTPHeaders()) + assert.Equal(t, api.DefaultVersion, apiclient.ClientVersion()) +} + +func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) { + customVersion := "v3.3.3" + defer patchEnvVariable(t, "DOCKER_API_VERSION", customVersion)() + + opts := &flags.CommonOptions{} + configFile := &configfile.ConfigFile{} + apiclient, err := NewAPIClientFromFlags(opts, configFile) + require.NoError(t, err) + assert.Equal(t, customVersion, apiclient.ClientVersion()) +} + +// TODO: move to gotestyourself +func patchEnvVariable(t *testing.T, key, value string) func() { + oldValue, ok := os.LookupEnv(key) + require.NoError(t, os.Setenv(key, value)) + return func() { + if !ok { + require.NoError(t, os.Unsetenv(key)) + return + } + require.NoError(t, os.Setenv(key, oldValue)) + } +} diff --git a/components/cli/cli/command/config/create_test.go b/components/cli/cli/command/config/create_test.go index f2338d0038..7bde47e694 100644 --- a/components/cli/cli/command/config/create_test.go +++ b/components/cli/cli/command/config/create_test.go @@ -7,11 +7,11 @@ import ( "strings" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "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/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -71,8 +71,7 @@ func TestConfigCreateWithName(t *testing.T) { 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)) + golden.Assert(t, string(actual), configDataFile) assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String())) } diff --git a/components/cli/cli/command/config/inspect_test.go b/components/cli/cli/command/config/inspect_test.go index e1af0b6b09..5ff280fd2a 100644 --- a/components/cli/cli/command/config/inspect_test.go +++ b/components/cli/cli/command/config/inspect_test.go @@ -1,19 +1,18 @@ package config import ( - "bytes" "fmt" "io/ioutil" "testing" "time" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -53,11 +52,10 @@ 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), + }), ) cmd.SetArgs(tc.args) for key, value := range tc.flags { @@ -95,17 +93,11 @@ func TestConfigInspectWithoutFormat(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newConfigInspectCommand( - test.NewFakeCliWithOutput(&fakeClient{ - configInspectFunc: tc.configInspectFunc, - }, buf), - ) + cli := test.NewFakeCli(&fakeClient{configInspectFunc: tc.configInspectFunc}) + cmd := newConfigInspectCommand(cli) cmd.SetArgs(tc.args) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("config-inspect-without-format.%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("config-inspect-without-format.%s.golden", tc.name)) } } @@ -135,18 +127,14 @@ func TestConfigInspectWithFormat(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newConfigInspectCommand( - test.NewFakeCliWithOutput(&fakeClient{ - configInspectFunc: tc.configInspectFunc, - }, buf), - ) + cli := test.NewFakeCli(&fakeClient{ + configInspectFunc: tc.configInspectFunc, + }) + cmd := newConfigInspectCommand(cli) cmd.SetArgs(tc.args) cmd.Flags().Set("format", tc.format) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("config-inspect-with-format.%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("config-inspect-with-format.%s.golden", tc.name)) } } @@ -172,16 +160,14 @@ func TestConfigInspectPretty(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newConfigInspectCommand( - test.NewFakeCliWithOutput(&fakeClient{ - configInspectFunc: tc.configInspectFunc, - }, buf)) + cli := test.NewFakeCli(&fakeClient{ + configInspectFunc: tc.configInspectFunc, + }) + cmd := newConfigInspectCommand(cli) + cmd.SetArgs([]string{"configID"}) cmd.Flags().Set("pretty", "true") assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("config-inspect-pretty.%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("config-inspect-pretty.%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/config/ls_test.go b/components/cli/cli/command/config/ls_test.go index c8393caca5..ac351cc725 100644 --- a/components/cli/cli/command/config/ls_test.go +++ b/components/cli/cli/command/config/ls_test.go @@ -6,14 +6,14 @@ import ( "time" "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -68,9 +68,7 @@ func TestConfigList(t *testing.T) { cmd := newConfigListCommand(cli) cmd.SetOutput(cli.OutBuffer()) assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "config-list.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "config-list.golden") } func TestConfigListWithQuietOption(t *testing.T) { @@ -87,9 +85,7 @@ func TestConfigListWithQuietOption(t *testing.T) { cmd := newConfigListCommand(cli) cmd.Flags().Set("quiet", "true") assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "config-list-with-quiet-option.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "config-list-with-quiet-option.golden") } func TestConfigListWithConfigFormat(t *testing.T) { @@ -108,9 +104,7 @@ func TestConfigListWithConfigFormat(t *testing.T) { }) cmd := newConfigListCommand(cli) assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "config-list-with-config-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "config-list-with-config-format.golden") } func TestConfigListWithFormat(t *testing.T) { @@ -127,9 +121,7 @@ func TestConfigListWithFormat(t *testing.T) { cmd := newConfigListCommand(cli) cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}") assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "config-list-with-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "config-list-with-format.golden") } func TestConfigListWithFilter(t *testing.T) { @@ -157,7 +149,5 @@ func TestConfigListWithFilter(t *testing.T) { cmd.Flags().Set("filter", "name=foo") cmd.Flags().Set("filter", "label=lbl1=Label-bar") assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "config-list-with-filter.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "config-list-with-filter.golden") } diff --git a/components/cli/cli/command/config/remove_test.go b/components/cli/cli/command/config/remove_test.go index 2b2b28ae11..f7142a2566 100644 --- a/components/cli/cli/command/config/remove_test.go +++ b/components/cli/cli/command/config/remove_test.go @@ -1,13 +1,12 @@ package config import ( - "bytes" "io/ioutil" "strings" "testing" - "github.com/docker/cli/cli/internal/test" - "github.com/docker/docker/pkg/testutil" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -31,11 +30,10 @@ 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), + }), ) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) @@ -45,27 +43,25 @@ func TestConfigRemoveErrors(t *testing.T) { 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 }, - }, buf) + }) cmd := newConfigRemoveCommand(cli) cmd.SetArgs(names) assert.NoError(t, cmd.Execute()) - assert.Equal(t, names, strings.Split(strings.TrimSpace(buf.String()), "\n")) + assert.Equal(t, names, strings.Split(strings.TrimSpace(cli.OutBuffer().String()), "\n")) assert.Equal(t, names, removedConfigs) } func TestConfigRemoveContinueAfterError(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) if name == "foo" { @@ -73,7 +69,7 @@ func TestConfigRemoveContinueAfterError(t *testing.T) { } return nil }, - }, buf) + }) cmd := newConfigRemoveCommand(cli) cmd.SetArgs(names) diff --git a/components/cli/cli/command/config/testdata/config-inspect-pretty.simple.golden b/components/cli/cli/command/config/testdata/config-inspect-pretty.simple.golden index d5eefec543..60b5c7fa4a 100644 --- a/components/cli/cli/command/config/testdata/config-inspect-pretty.simple.golden +++ b/components/cli/cli/command/config/testdata/config-inspect-pretty.simple.golden @@ -1,8 +1,8 @@ -ID: configID -Name: configName +ID: configID +Name: configName Labels: - - lbl1=value1 -Created at: 0001-01-01 00:00:00+0000 utc -Updated at: 0001-01-01 00:00:00+0000 utc + - lbl1=value1 +Created at: 0001-01-01 00:00:00 +0000 utc +Updated at: 0001-01-01 00:00:00 +0000 utc Data: payload here diff --git a/components/cli/cli/command/config/testdata/config-inspect-without-format.multiple-configs-with-labels.golden b/components/cli/cli/command/config/testdata/config-inspect-without-format.multiple-configs-with-labels.golden index 6887c185f1..b01a400c57 100644 --- a/components/cli/cli/command/config/testdata/config-inspect-without-format.multiple-configs-with-labels.golden +++ b/components/cli/cli/command/config/testdata/config-inspect-without-format.multiple-configs-with-labels.golden @@ -1,7 +1,7 @@ [ { "ID": "ID-foo", - "Version": {}, + "Version": {}, "CreatedAt": "0001-01-01T00:00:00Z", "UpdatedAt": "0001-01-01T00:00:00Z", "Spec": { @@ -13,7 +13,7 @@ }, { "ID": "ID-bar", - "Version": {}, + "Version": {}, "CreatedAt": "0001-01-01T00:00:00Z", "UpdatedAt": "0001-01-01T00:00:00Z", "Spec": { diff --git a/components/cli/cli/command/config/testdata/config-inspect-without-format.single-config.golden b/components/cli/cli/command/config/testdata/config-inspect-without-format.single-config.golden index ea42ec6f4f..c4f41c1067 100644 --- a/components/cli/cli/command/config/testdata/config-inspect-without-format.single-config.golden +++ b/components/cli/cli/command/config/testdata/config-inspect-without-format.single-config.golden @@ -1,9 +1,9 @@ [ { "ID": "ID-foo", - "Version": {}, - "CreatedAt": "0001-01-01T00:00:00Z", - "UpdatedAt": "0001-01-01T00:00:00Z", + "Version": {}, + "CreatedAt": "0001-01-01T00:00:00Z", + "UpdatedAt": "0001-01-01T00:00:00Z", "Spec": { "Name": "foo", "Labels": null diff --git a/components/cli/cli/command/config/testdata/config-list-with-config-format.golden b/components/cli/cli/command/config/testdata/config-list-with-config-format.golden index 9a47538804..11c39229b8 100644 --- a/components/cli/cli/command/config/testdata/config-list-with-config-format.golden +++ b/components/cli/cli/command/config/testdata/config-list-with-config-format.golden @@ -1,2 +1,2 @@ -foo +foo bar label=label-bar diff --git a/components/cli/cli/command/config/testdata/config-list-with-format.golden b/components/cli/cli/command/config/testdata/config-list-with-format.golden index 9a47538804..11c39229b8 100644 --- a/components/cli/cli/command/config/testdata/config-list-with-format.golden +++ b/components/cli/cli/command/config/testdata/config-list-with-format.golden @@ -1,2 +1,2 @@ -foo +foo bar label=label-bar diff --git a/components/cli/cli/command/container/attach.go b/components/cli/cli/command/container/attach.go index dce6432846..372fcbb348 100644 --- a/components/cli/cli/command/container/attach.go +++ b/components/cli/cli/command/container/attach.go @@ -120,18 +120,7 @@ func runAttach(dockerCli command.Cli, opts *attachOptions) error { } 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 - // terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially - // resize it, then go back to normal. Without this, every attach after the first will - // require the user to manually resize or hit enter. - resizeTtyTo(ctx, client, opts.container, height+1, width+1, false) - - // After the above resizing occurs, the call to MonitorTtySize below will handle resetting back - // to the actual size. - if err := MonitorTtySize(ctx, dockerCli, opts.container, false); err != nil { - logrus.Debugf("Error monitoring TTY size: %s", err) - } + resizeTTY(ctx, dockerCli, opts.container) } streamer := hijackedIOStreamer{ @@ -151,14 +140,36 @@ func runAttach(dockerCli command.Cli, opts *attachOptions) error { if errAttach != nil { return errAttach } + return getExitStatus(ctx, dockerCli.Client(), opts.container) +} - _, status, err := getExitCode(ctx, dockerCli, opts.container) - if err != nil { - return err +func resizeTTY(ctx context.Context, dockerCli command.Cli, containerID string) { + height, width := dockerCli.Out().GetTtySize() + // To handle the case where a user repeatedly attaches/detaches without resizing their + // terminal, the only way to get the shell prompt to display for attaches 2+ is to artificially + // resize it, then go back to normal. Without this, every attach after the first will + // require the user to manually resize or hit enter. + resizeTtyTo(ctx, dockerCli.Client(), containerID, height+1, width+1, false) + + // After the above resizing occurs, the call to MonitorTtySize below will handle resetting back + // to the actual size. + if err := MonitorTtySize(ctx, dockerCli, containerID, false); err != nil { + logrus.Debugf("Error monitoring TTY size: %s", err) } +} + +func getExitStatus(ctx context.Context, apiclient client.ContainerAPIClient, containerID string) error { + container, err := apiclient.ContainerInspect(ctx, containerID) + if err != nil { + // If we can't connect, then the daemon probably died. + if !client.IsErrConnectionFailed(err) { + return err + } + return cli.StatusError{StatusCode: -1} + } + status := container.State.ExitCode if status != 0 { return cli.StatusError{StatusCode: status} } - return nil } diff --git a/components/cli/cli/command/container/attach_test.go b/components/cli/cli/command/container/attach_test.go index a33eeeeb54..1ca775c6d5 100644 --- a/components/cli/cli/command/container/attach_test.go +++ b/components/cli/cli/command/container/attach_test.go @@ -4,10 +4,13 @@ import ( "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/cli" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" ) func TestNewAttachCommandErrors(t *testing.T) { @@ -67,9 +70,48 @@ func TestNewAttachCommandErrors(t *testing.T) { }, } for _, tc := range testCases { - cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc})) + cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc})) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) } } + +func TestGetExitStatus(t *testing.T) { + containerID := "the exec id" + expecatedErr := errors.New("unexpected error") + + testcases := []struct { + inspectError error + exitCode int + expectedError error + }{ + { + inspectError: nil, + exitCode: 0, + }, + { + inspectError: expecatedErr, + expectedError: expecatedErr, + }, + { + exitCode: 15, + expectedError: cli.StatusError{StatusCode: 15}, + }, + } + + for _, testcase := range testcases { + client := &fakeClient{ + inspectFunc: func(id string) (types.ContainerJSON, error) { + assert.Equal(t, containerID, id) + return types.ContainerJSON{ + ContainerJSONBase: &types.ContainerJSONBase{ + State: &types.ContainerState{ExitCode: testcase.exitCode}, + }, + }, testcase.inspectError + }, + } + err := getExitStatus(context.Background(), client, containerID) + assert.Equal(t, testcase.expectedError, err) + } +} diff --git a/components/cli/cli/command/container/client_test.go b/components/cli/cli/command/container/client_test.go index 1e2d5d9cf8..32f9a28fbf 100644 --- a/components/cli/cli/command/container/client_test.go +++ b/components/cli/cli/command/container/client_test.go @@ -8,12 +8,32 @@ import ( type fakeClient struct { client.Client - containerInspectFunc func(string) (types.ContainerJSON, error) + inspectFunc func(string) (types.ContainerJSON, error) + execInspectFunc func(execID string) (types.ContainerExecInspect, error) + execCreateFunc func(container string, config types.ExecConfig) (types.IDResponse, error) } -func (cli *fakeClient) ContainerInspect(_ context.Context, containerID string) (types.ContainerJSON, error) { - if cli.containerInspectFunc != nil { - return cli.containerInspectFunc(containerID) +func (f *fakeClient) ContainerInspect(_ context.Context, containerID string) (types.ContainerJSON, error) { + if f.inspectFunc != nil { + return f.inspectFunc(containerID) } return types.ContainerJSON{}, nil } + +func (f *fakeClient) ContainerExecCreate(_ context.Context, container string, config types.ExecConfig) (types.IDResponse, error) { + if f.execCreateFunc != nil { + return f.execCreateFunc(container, config) + } + return types.IDResponse{}, nil +} + +func (f *fakeClient) ContainerExecInspect(_ context.Context, execID string) (types.ContainerExecInspect, error) { + if f.execInspectFunc != nil { + return f.execInspectFunc(execID) + } + return types.ContainerExecInspect{}, nil +} + +func (f *fakeClient) ContainerExecStart(ctx context.Context, execID string, config types.ExecStartCheck) error { + return nil +} diff --git a/components/cli/cli/command/container/create.go b/components/cli/cli/command/container/create.go index 2795e4fed7..97bc1863f0 100644 --- a/components/cli/cli/command/container/create.go +++ b/components/cli/cli/command/container/create.go @@ -113,6 +113,9 @@ type cidFile struct { } func (cid *cidFile) Close() error { + if cid.file == nil { + return nil + } cid.file.Close() if cid.written { @@ -126,6 +129,9 @@ func (cid *cidFile) Close() error { } func (cid *cidFile) Write(id string) error { + if cid.file == nil { + return nil + } if _, err := cid.file.Write([]byte(id)); err != nil { return errors.Errorf("Failed to write the container ID to the file: %s", err) } @@ -134,6 +140,9 @@ func (cid *cidFile) Write(id string) error { } func newCIDFile(path string) (*cidFile, error) { + if path == "" { + return &cidFile{}, nil + } if _, err := os.Stat(path); err == nil { return nil, errors.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path) } @@ -153,19 +162,15 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig stderr := dockerCli.Err() var ( - containerIDFile *cidFile - trustedRef reference.Canonical - namedRef reference.Named + trustedRef reference.Canonical + namedRef reference.Named ) - cidfile := hostConfig.ContainerIDFile - if cidfile != "" { - var err error - if containerIDFile, err = newCIDFile(cidfile); err != nil { - return nil, err - } - defer containerIDFile.Close() + containerIDFile, err := newCIDFile(hostConfig.ContainerIDFile) + if err != nil { + return nil, err } + defer containerIDFile.Close() ref, err := reference.ParseAnyReference(config.Image) if err != nil { @@ -207,18 +212,13 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig if retryErr != nil { return nil, retryErr } - } else { - return nil, err } + return nil, err } for _, warning := range response.Warnings { fmt.Fprintf(stderr, "WARNING: %s\n", warning) } - if containerIDFile != nil { - if err = containerIDFile.Write(response.ID); err != nil { - return nil, err - } - } - return &response, nil + err = containerIDFile.Write(response.ID) + return &response, err } diff --git a/components/cli/cli/command/container/create_test.go b/components/cli/cli/command/container/create_test.go new file mode 100644 index 0000000000..467cdb3a6e --- /dev/null +++ b/components/cli/cli/command/container/create_test.go @@ -0,0 +1,64 @@ +package container + +import ( + "os" + "testing" + + "io/ioutil" + + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/fs" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCIDFileNoOPWithNoFilename(t *testing.T) { + file, err := newCIDFile("") + require.NoError(t, err) + assert.Equal(t, &cidFile{}, file) + + assert.NoError(t, file.Write("id")) + assert.NoError(t, file.Close()) +} + +func TestNewCIDFileWhenFileAlreadyExists(t *testing.T) { + tempfile := fs.NewFile(t, "test-cid-file") + defer tempfile.Remove() + + _, err := newCIDFile(tempfile.Path()) + testutil.ErrorContains(t, err, "Container ID file found") +} + +func TestCIDFileCloseWithNoWrite(t *testing.T) { + tempdir := fs.NewDir(t, "test-cid-file") + defer tempdir.Remove() + + path := tempdir.Join("cidfile") + file, err := newCIDFile(path) + require.NoError(t, err) + assert.Equal(t, file.path, path) + + assert.NoError(t, file.Close()) + _, err = os.Stat(path) + assert.True(t, os.IsNotExist(err)) +} + +func TestCIDFileCloseWithWrite(t *testing.T) { + tempdir := fs.NewDir(t, "test-cid-file") + defer tempdir.Remove() + + path := tempdir.Join("cidfile") + file, err := newCIDFile(path) + require.NoError(t, err) + + content := "id" + assert.NoError(t, file.Write(content)) + + actual, err := ioutil.ReadFile(path) + require.NoError(t, err) + assert.Equal(t, content, string(actual)) + + assert.NoError(t, file.Close()) + _, err = os.Stat(path) + require.NoError(t, err) +} diff --git a/components/cli/cli/command/container/exec.go b/components/cli/cli/command/container/exec.go index bbcd34ae0c..feb341a75d 100644 --- a/components/cli/cli/command/container/exec.go +++ b/components/cli/cli/command/container/exec.go @@ -7,10 +7,12 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/opts" "github.com/docker/docker/api/types" apiclient "github.com/docker/docker/client" "github.com/docker/docker/pkg/promise" + "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" ) @@ -22,14 +24,13 @@ type execOptions struct { detach bool user string privileged bool - env *opts.ListOpts + env opts.ListOpts + container string + command []string } -func newExecOptions() *execOptions { - var values []string - return &execOptions{ - env: opts.NewListOptsRef(&values, opts.ValidateEnv), - } +func newExecOptions() execOptions { + return execOptions{env: opts.NewListOpts(opts.ValidateEnv)} } // NewExecCommand creates a new cobra.Command for `docker exec` @@ -41,9 +42,9 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command { Short: "Run a command in a running container", Args: cli.RequiresMinArgs(2), RunE: func(cmd *cobra.Command, args []string) error { - container := args[0] - execCmd := args[1:] - return runExec(dockerCli, options, container, execCmd) + options.container = args[0] + options.command = args[1:] + return runExec(dockerCli, options) }, } @@ -56,27 +57,14 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command { flags.BoolVarP(&options.detach, "detach", "d", false, "Detached mode: run command in the background") flags.StringVarP(&options.user, "user", "u", "", "Username or UID (format: [:])") flags.BoolVarP(&options.privileged, "privileged", "", false, "Give extended privileges to the command") - flags.VarP(options.env, "env", "e", "Set environment variables") + flags.VarP(&options.env, "env", "e", "Set environment variables") flags.SetAnnotation("env", "version", []string{"1.25"}) return cmd } -// nolint: gocyclo -func runExec(dockerCli command.Cli, options *execOptions, container string, execCmd []string) error { - execConfig, err := parseExec(options, execCmd) - // just in case the ParseExec does not exit - if container == "" || err != nil { - return cli.StatusError{StatusCode: 1} - } - - if options.detachKeys != "" { - dockerCli.ConfigFile().DetachKeys = options.detachKeys - } - - // Send client escape keys - execConfig.DetachKeys = dockerCli.ConfigFile().DetachKeys - +func runExec(dockerCli command.Cli, options execOptions) error { + execConfig := parseExec(options, dockerCli.ConfigFile()) ctx := context.Background() client := dockerCli.Client() @@ -84,7 +72,7 @@ func runExec(dockerCli command.Cli, options *execOptions, container string, exec // otherwise if we error out we will leak execIDs on the server (and // there's no easy way to clean those up). But also in order to make "not // exist" errors take precedence we do a dummy inspect first. - if _, err := client.ContainerInspect(ctx, container); err != nil { + if _, err := client.ContainerInspect(ctx, options.container); err != nil { return err } if !execConfig.Detach { @@ -93,27 +81,27 @@ func runExec(dockerCli command.Cli, options *execOptions, container string, exec } } - response, err := client.ContainerExecCreate(ctx, container, *execConfig) + response, err := client.ContainerExecCreate(ctx, options.container, *execConfig) if err != nil { return err } execID := response.ID if execID == "" { - fmt.Fprintln(dockerCli.Out(), "exec ID empty") - return nil + return errors.New("exec ID empty") } - // Temp struct for execStart so that we don't need to transfer all the execConfig. if execConfig.Detach { execStartCheck := types.ExecStartCheck{ Detach: execConfig.Detach, Tty: execConfig.Tty, } - return client.ContainerExecStart(ctx, execID, execStartCheck) } + return interactiveExec(ctx, dockerCli, execConfig, execID) +} +func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *types.ExecConfig, execID string) error { // Interactive exec requested. var ( out, stderr io.Writer @@ -135,6 +123,7 @@ func runExec(dockerCli command.Cli, options *execOptions, container string, exec } } + client := dockerCli.Client() resp, err := client.ContainerExecAttach(ctx, execID, *execConfig) if err != nil { return err @@ -165,42 +154,35 @@ func runExec(dockerCli command.Cli, options *execOptions, container string, exec return err } - var status int - if _, status, err = getExecExitCode(ctx, client, execID); err != nil { - return err - } - - if status != 0 { - return cli.StatusError{StatusCode: status} - } - - return nil + return getExecExitStatus(ctx, client, execID) } -// getExecExitCode perform an inspect on the exec command. It returns -// the running state and the exit code. -func getExecExitCode(ctx context.Context, client apiclient.ContainerAPIClient, execID string) (bool, int, error) { +func getExecExitStatus(ctx context.Context, client apiclient.ContainerAPIClient, execID string) error { resp, err := client.ContainerExecInspect(ctx, execID) if err != nil { // If we can't connect, then the daemon probably died. if !apiclient.IsErrConnectionFailed(err) { - return false, -1, err + return err } - return false, -1, nil + return cli.StatusError{StatusCode: -1} } - - return resp.Running, resp.ExitCode, nil + status := resp.ExitCode + if status != 0 { + return cli.StatusError{StatusCode: status} + } + return nil } // parseExec parses the specified args for the specified command and generates // an ExecConfig from it. -func parseExec(opts *execOptions, execCmd []string) (*types.ExecConfig, error) { +func parseExec(opts execOptions, configFile *configfile.ConfigFile) *types.ExecConfig { execConfig := &types.ExecConfig{ User: opts.user, Privileged: opts.privileged, Tty: opts.tty, - Cmd: execCmd, + Cmd: opts.command, Detach: opts.detach, + Env: opts.env.GetAll(), } // If -d is not set, attach to everything by default @@ -212,9 +194,10 @@ func parseExec(opts *execOptions, execCmd []string) (*types.ExecConfig, error) { } } - if opts.env != nil { - execConfig.Env = opts.env.GetAll() + if opts.detachKeys != "" { + execConfig.DetachKeys = opts.detachKeys + } else { + execConfig.DetachKeys = configFile.DetachKeys } - - return execConfig, nil + return execConfig } diff --git a/components/cli/cli/command/container/exec_test.go b/components/cli/cli/command/container/exec_test.go index 0eed2ff0f7..492e3a558d 100644 --- a/components/cli/cli/command/container/exec_test.go +++ b/components/cli/cli/command/container/exec_test.go @@ -4,118 +4,201 @@ import ( "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/cli" + "github.com/docker/cli/cli/config/configfile" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" + "github.com/docker/cli/opts" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" "github.com/pkg/errors" - "github.com/stretchr/testify/require" + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" ) -type arguments struct { - options execOptions - execCmd []string +func withDefaultOpts(options execOptions) execOptions { + options.env = opts.NewListOpts(opts.ValidateEnv) + if len(options.command) == 0 { + options.command = []string{"command"} + } + return options } func TestParseExec(t *testing.T) { - valids := map[*arguments]*types.ExecConfig{ + testcases := []struct { + options execOptions + configFile configfile.ConfigFile + expected types.ExecConfig + }{ { - execCmd: []string{"command"}, - }: { - Cmd: []string{"command"}, - AttachStdout: true, - AttachStderr: true, + expected: types.ExecConfig{ + Cmd: []string{"command"}, + AttachStdout: true, + AttachStderr: true, + }, + options: withDefaultOpts(execOptions{}), }, { - execCmd: []string{"command1", "command2"}, - }: { - Cmd: []string{"command1", "command2"}, - AttachStdout: true, - AttachStderr: true, + expected: types.ExecConfig{ + Cmd: []string{"command1", "command2"}, + AttachStdout: true, + AttachStderr: true, + }, + options: withDefaultOpts(execOptions{ + command: []string{"command1", "command2"}, + }), }, { - options: execOptions{ + options: withDefaultOpts(execOptions{ interactive: true, tty: true, user: "uid", + }), + expected: types.ExecConfig{ + User: "uid", + AttachStdin: true, + AttachStdout: true, + AttachStderr: true, + Tty: true, + Cmd: []string{"command"}, }, - execCmd: []string{"command"}, - }: { - User: "uid", - AttachStdin: true, - AttachStdout: true, - AttachStderr: true, - Tty: true, - Cmd: []string{"command"}, }, { - options: execOptions{ - detach: true, + options: withDefaultOpts(execOptions{detach: true}), + expected: types.ExecConfig{ + Detach: true, + Cmd: []string{"command"}, }, - execCmd: []string{"command"}, - }: { - AttachStdin: false, - AttachStdout: false, - AttachStderr: false, - Detach: true, - Cmd: []string{"command"}, }, { - options: execOptions{ + options: withDefaultOpts(execOptions{ tty: true, interactive: true, detach: true, + }), + expected: types.ExecConfig{ + Detach: true, + Tty: true, + Cmd: []string{"command"}, + }, + }, + { + options: withDefaultOpts(execOptions{detach: true}), + configFile: configfile.ConfigFile{DetachKeys: "de"}, + expected: types.ExecConfig{ + Cmd: []string{"command"}, + DetachKeys: "de", + Detach: true, + }, + }, + { + options: withDefaultOpts(execOptions{ + detach: true, + detachKeys: "ab", + }), + configFile: configfile.ConfigFile{DetachKeys: "de"}, + expected: types.ExecConfig{ + Cmd: []string{"command"}, + DetachKeys: "ab", + Detach: true, }, - execCmd: []string{"command"}, - }: { - AttachStdin: false, - AttachStdout: false, - AttachStderr: false, - Detach: true, - Tty: true, - Cmd: []string{"command"}, }, } - for valid, expectedExecConfig := range valids { - execConfig, err := parseExec(&valid.options, valid.execCmd) - require.NoError(t, err) - if !compareExecConfig(expectedExecConfig, execConfig) { - t.Fatalf("Expected [%v] for %v, got [%v]", expectedExecConfig, valid, execConfig) - } + for _, testcase := range testcases { + execConfig := parseExec(testcase.options, &testcase.configFile) + assert.Equal(t, testcase.expected, *execConfig) } } -func compareExecConfig(config1 *types.ExecConfig, config2 *types.ExecConfig) bool { - if config1.AttachStderr != config2.AttachStderr { - return false +func TestRunExec(t *testing.T) { + var testcases = []struct { + doc string + options execOptions + client fakeClient + expectedError string + expectedOut string + expectedErr string + }{ + { + doc: "successful detach", + options: withDefaultOpts(execOptions{ + container: "thecontainer", + detach: true, + }), + client: fakeClient{execCreateFunc: execCreateWithID}, + }, + { + doc: "inspect error", + options: newExecOptions(), + client: fakeClient{ + inspectFunc: func(string) (types.ContainerJSON, error) { + return types.ContainerJSON{}, errors.New("failed inspect") + }, + }, + expectedError: "failed inspect", + }, + { + doc: "missing exec ID", + options: newExecOptions(), + expectedError: "exec ID empty", + }, } - if config1.AttachStdin != config2.AttachStdin { - return false + + for _, testcase := range testcases { + t.Run(testcase.doc, func(t *testing.T) { + cli := test.NewFakeCli(&testcase.client) + + err := runExec(cli, testcase.options) + if testcase.expectedError != "" { + testutil.ErrorContains(t, err, testcase.expectedError) + } else { + if !assert.NoError(t, err) { + return + } + } + assert.Equal(t, testcase.expectedOut, cli.OutBuffer().String()) + assert.Equal(t, testcase.expectedErr, cli.ErrBuffer().String()) + }) } - if config1.AttachStdout != config2.AttachStdout { - return false +} + +func execCreateWithID(_ string, _ types.ExecConfig) (types.IDResponse, error) { + return types.IDResponse{ID: "execid"}, nil +} + +func TestGetExecExitStatus(t *testing.T) { + execID := "the exec id" + expecatedErr := errors.New("unexpected error") + + testcases := []struct { + inspectError error + exitCode int + expectedError error + }{ + { + inspectError: nil, + exitCode: 0, + }, + { + inspectError: expecatedErr, + expectedError: expecatedErr, + }, + { + exitCode: 15, + expectedError: cli.StatusError{StatusCode: 15}, + }, } - if config1.Detach != config2.Detach { - return false - } - if config1.Privileged != config2.Privileged { - return false - } - if config1.Tty != config2.Tty { - return false - } - if config1.User != config2.User { - return false - } - if len(config1.Cmd) != len(config2.Cmd) { - return false - } - for index, value := range config1.Cmd { - if value != config2.Cmd[index] { - return false + + for _, testcase := range testcases { + client := &fakeClient{ + execInspectFunc: func(id string) (types.ContainerExecInspect, error) { + assert.Equal(t, execID, id) + return types.ContainerExecInspect{ExitCode: testcase.exitCode}, testcase.inspectError + }, } + err := getExecExitStatus(context.Background(), client, execID) + assert.Equal(t, testcase.expectedError, err) } - return true } func TestNewExecCommandErrors(t *testing.T) { @@ -135,7 +218,7 @@ func TestNewExecCommandErrors(t *testing.T) { }, } for _, tc := range testCases { - cli := test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc}) + cli := test.NewFakeCli(&fakeClient{inspectFunc: tc.containerInspectFunc}) cmd := NewExecCommand(cli) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) diff --git a/components/cli/cli/command/container/opts.go b/components/cli/cli/command/container/opts.go index cf1f931b29..f80474d3fe 100644 --- a/components/cli/cli/command/container/opts.go +++ b/components/cli/cli/command/container/opts.go @@ -274,7 +274,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions { // Low-level execution (cgroups, namespaces, ...) flags.StringVar(&copts.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container") - flags.StringVar(&copts.ipcMode, "ipc", "", "IPC namespace to use") + flags.StringVar(&copts.ipcMode, "ipc", "", "IPC mode to use") flags.StringVar(&copts.isolation, "isolation", "", "Container isolation technology") flags.StringVar(&copts.pidMode, "pid", "", "PID namespace to use") flags.Var(&copts.shmSize, "shm-size", "Size of /dev/shm") @@ -421,11 +421,6 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err return nil, err } - ipcMode := container.IpcMode(copts.ipcMode) - if !ipcMode.Valid() { - return nil, errors.Errorf("--ipc: invalid IPC mode") - } - pidMode := container.PidMode(copts.pidMode) if !pidMode.Valid() { return nil, errors.Errorf("--pid: invalid PID mode") @@ -584,7 +579,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err ExtraHosts: copts.extraHosts.GetAll(), VolumesFrom: copts.volumesFrom.GetAll(), NetworkMode: container.NetworkMode(copts.netMode), - IpcMode: ipcMode, + IpcMode: container.IpcMode(copts.ipcMode), PidMode: pidMode, UTSMode: utsMode, UsernsMode: usernsMode, diff --git a/components/cli/cli/command/container/opts_test.go b/components/cli/cli/command/container/opts_test.go index 25e5b04fc8..08c1d07da3 100644 --- a/components/cli/cli/command/container/opts_test.go +++ b/components/cli/cli/command/container/opts_test.go @@ -1,8 +1,6 @@ package container import ( - "bytes" - "encoding/json" "fmt" "io/ioutil" "os" @@ -11,10 +9,9 @@ import ( "testing" "time" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types/container" networktypes "github.com/docker/docker/api/types/network" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/runconfig" "github.com/docker/go-connections/nat" "github.com/pkg/errors" "github.com/spf13/pflag" @@ -115,7 +112,7 @@ func TestParseRunWithInvalidArgs(t *testing.T) { } // nolint: gocyclo -func TestParseRunVolumes(t *testing.T) { +func TestParseWithVolumes(t *testing.T) { // A single volume arr, tryit := setupPlatformVolume([]string{`/tmp`}, []string{`c:\tmp`}) @@ -135,19 +132,19 @@ func TestParseRunVolumes(t *testing.T) { t.Fatalf("Error parsing volume flags, %s is missing from volumes. Received %v", arr[1], config.Volumes) } - // A single bind-mount + // A single bind mount arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`}, []string{os.Getenv("TEMP") + `:c:\containerTmp`}) if config, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || hostConfig.Binds[0] != arr[0] { t.Fatalf("Error parsing volume flags, %q should mount-bind the path before the colon into the path after the colon. Received %v %v", arr[0], hostConfig.Binds, config.Volumes) } - // Two bind-mounts. + // Two bind mounts. arr, tryit = setupPlatformVolume([]string{`/hostTmp:/containerTmp`, `/hostVar:/containerVar`}, []string{os.Getenv("ProgramData") + `:c:\ContainerPD`, os.Getenv("TEMP") + `:c:\containerTmp`}) if _, hostConfig := mustParse(t, tryit); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], arr[0], arr[1]) != nil { t.Fatalf("Error parsing volume flags, `%s and %s` did not mount-bind correctly. Received %v", arr[0], arr[1], hostConfig.Binds) } - // Two bind-mounts, first read-only, second read-write. + // Two bind mounts, first read-only, second read-write. // TODO Windows: The Windows version uses read-write as that's the only mode it supports. Can change this post TP4 arr, tryit = setupPlatformVolume( []string{`/hostTmp:/containerTmp:ro`, `/hostVar:/containerVar:rw`}, @@ -366,23 +363,12 @@ 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") - - // ipc ok - _, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"}) - require.NoError(t, err) - if !hostconfig.IpcMode.Valid() { - t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode) - } - // pid ko - _, _, _, err = parseRun([]string{"--pid=container:", "img", "cmd"}) + _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}) testutil.ErrorContains(t, err, "--pid: invalid PID mode") // pid ok - _, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"}) + _, hostconfig, _, err := parseRun([]string{"--pid=host", "img", "cmd"}) require.NoError(t, err) if !hostconfig.PidMode.Valid() { t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode) @@ -595,161 +581,6 @@ func TestParseEntryPoint(t *testing.T) { } } -// This tests the cases for binds which are generated through -// DecodeContainerConfig rather than Parse() -// nolint: gocyclo -func TestDecodeContainerConfigVolumes(t *testing.T) { - - // Root to root - bindsOrVols, _ := setupPlatformVolume([]string{`/:/`}, []string{os.Getenv("SystemDrive") + `\:c:\`}) - if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { - t.Fatalf("binds %v should have failed", bindsOrVols) - } - if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { - t.Fatalf("volume %v should have failed", bindsOrVols) - } - - // No destination path - bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:`}, []string{os.Getenv("TEMP") + `\:`}) - if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { - t.Fatalf("binds %v should have failed", bindsOrVols) - } - if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { - t.Fatalf("volume %v should have failed", bindsOrVols) - } - - // // No destination path or mode - bindsOrVols, _ = setupPlatformVolume([]string{`/tmp::`}, []string{os.Getenv("TEMP") + `\::`}) - if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { - t.Fatalf("binds %v should have failed", bindsOrVols) - } - if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { - t.Fatalf("volume %v should have failed", bindsOrVols) - } - - // A whole lot of nothing - bindsOrVols = []string{`:`} - if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { - t.Fatalf("binds %v should have failed", bindsOrVols) - } - if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { - t.Fatalf("volume %v should have failed", bindsOrVols) - } - - // A whole lot of nothing with no mode - bindsOrVols = []string{`::`} - if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { - t.Fatalf("binds %v should have failed", bindsOrVols) - } - if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { - t.Fatalf("volume %v should have failed", bindsOrVols) - } - - // Too much including an invalid mode - wTmp := os.Getenv("TEMP") - bindsOrVols, _ = setupPlatformVolume([]string{`/tmp:/tmp:/tmp:/tmp`}, []string{wTmp + ":" + wTmp + ":" + wTmp + ":" + wTmp}) - if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { - t.Fatalf("binds %v should have failed", bindsOrVols) - } - if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { - t.Fatalf("volume %v should have failed", bindsOrVols) - } - - // Windows specific error tests - if runtime.GOOS == "windows" { - // Volume which does not include a drive letter - bindsOrVols = []string{`\tmp`} - if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { - t.Fatalf("binds %v should have failed", bindsOrVols) - } - if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { - t.Fatalf("volume %v should have failed", bindsOrVols) - } - - // Root to C-Drive - bindsOrVols = []string{os.Getenv("SystemDrive") + `\:c:`} - if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { - t.Fatalf("binds %v should have failed", bindsOrVols) - } - if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { - t.Fatalf("volume %v should have failed", bindsOrVols) - } - - // Container path that does not include a drive letter - bindsOrVols = []string{`c:\windows:\somewhere`} - if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { - t.Fatalf("binds %v should have failed", bindsOrVols) - } - if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { - t.Fatalf("volume %v should have failed", bindsOrVols) - } - } - - // Linux-specific error tests - if runtime.GOOS != "windows" { - // Just root - bindsOrVols = []string{`/`} - if _, _, err := callDecodeContainerConfig(nil, bindsOrVols); err == nil { - t.Fatalf("binds %v should have failed", bindsOrVols) - } - if _, _, err := callDecodeContainerConfig(bindsOrVols, nil); err == nil { - t.Fatalf("volume %v should have failed", bindsOrVols) - } - - // A single volume that looks like a bind mount passed in Volumes. - // This should be handled as a bind mount, not a volume. - vols := []string{`/foo:/bar`} - if config, hostConfig, err := callDecodeContainerConfig(vols, nil); err != nil { - t.Fatal("Volume /foo:/bar should have succeeded as a volume name") - } else if hostConfig.Binds != nil { - t.Fatalf("Error parsing volume flags, /foo:/bar should not mount-bind anything. Received %v", hostConfig.Binds) - } else if _, exists := config.Volumes[vols[0]]; !exists { - t.Fatalf("Error parsing volume flags, /foo:/bar is missing from volumes. Received %v", config.Volumes) - } - - } -} - -// callDecodeContainerConfig is a utility function used by TestDecodeContainerConfigVolumes -// to call DecodeContainerConfig. It effectively does what a client would -// do when calling the daemon by constructing a JSON stream of a -// ContainerConfigWrapper which is populated by the set of volume specs -// passed into it. It returns a config and a hostconfig which can be -// validated to ensure DecodeContainerConfig has manipulated the structures -// correctly. -func callDecodeContainerConfig(volumes []string, binds []string) (*container.Config, *container.HostConfig, error) { - var ( - b []byte - err error - c *container.Config - h *container.HostConfig - ) - w := runconfig.ContainerConfigWrapper{ - Config: &container.Config{ - Volumes: map[string]struct{}{}, - }, - HostConfig: &container.HostConfig{ - NetworkMode: "none", - Binds: binds, - }, - } - for _, v := range volumes { - w.Config.Volumes[v] = struct{}{} - } - if b, err = json.Marshal(w); err != nil { - return nil, nil, errors.Errorf("Error on marshal %s", err.Error()) - } - c, h, _, err = runconfig.DecodeContainerConfig(bytes.NewReader(b)) - if err != nil { - return nil, nil, errors.Errorf("Error parsing %s: %v", string(b), err) - } - if c == nil || h == nil { - return nil, nil, errors.Errorf("Empty config or hostconfig") - } - - return c, h, err -} - func TestValidateDevice(t *testing.T) { valid := []string{ "/home", diff --git a/components/cli/cli/command/container/utils.go b/components/cli/cli/command/container/utils.go index d9afe24163..1109b66b21 100644 --- a/components/cli/cli/command/container/utils.go +++ b/components/cli/cli/command/container/utils.go @@ -10,7 +10,6 @@ import ( "github.com/docker/docker/api/types/events" "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/versions" - clientapi "github.com/docker/docker/client" "golang.org/x/net/context" ) @@ -125,20 +124,6 @@ func legacyWaitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, return statusChan } -// 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) { - c, err := dockerCli.Client().ContainerInspect(ctx, containerID) - if err != nil { - // If we can't connect, then the daemon probably died. - if !clientapi.IsErrConnectionFailed(err) { - return false, -1, err - } - return false, -1, nil - } - return c.State.Running, c.State.ExitCode, nil -} - func parallelOperation(ctx context.Context, containers []string, op func(ctx context.Context, container string) error) chan error { if len(containers) == 0 { return nil diff --git a/components/cli/cli/command/formatter/custom_test.go b/components/cli/cli/command/formatter/custom_test.go index da42039dca..a9f6ccdac9 100644 --- a/components/cli/cli/command/formatter/custom_test.go +++ b/components/cli/cli/command/formatter/custom_test.go @@ -1,9 +1,10 @@ package formatter import ( - "reflect" "strings" "testing" + + "github.com/stretchr/testify/assert" ) func compareMultipleValues(t *testing.T, value, expected string) { @@ -22,7 +23,5 @@ func compareMultipleValues(t *testing.T, value, expected string) { keyval := strings.Split(expected, "=") expMap[keyval[0]] = keyval[1] } - if !reflect.DeepEqual(expMap, entriesMap) { - t.Fatalf("Expected entries: %v, got: %v", expected, value) - } + assert.Equal(t, expMap, entriesMap) } diff --git a/components/cli/cli/command/formatter/image.go b/components/cli/cli/command/formatter/image.go index cbd3edad3c..e94785ef08 100644 --- a/components/cli/cli/command/formatter/image.go +++ b/components/cli/cli/command/formatter/image.go @@ -79,11 +79,16 @@ func ImageWrite(ctx ImageContext, images []types.ImageSummary) error { return ctx.Write(newImageContext(), render) } +// needDigest determines whether the image digest should be ignored or not when writing image context +func needDigest(ctx ImageContext) bool { + return ctx.Digest || ctx.Format.Contains("{{.Digest}}") +} + func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subContext subContext) error) error { for _, image := range images { - images := []*imageContext{} + formatted := []*imageContext{} if isDangling(image) { - images = append(images, &imageContext{ + formatted = append(formatted, &imageContext{ trunc: ctx.Trunc, i: image, repo: "", @@ -91,90 +96,9 @@ func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subC digest: "", }) } else { - repoTags := map[string][]string{} - repoDigests := map[string][]string{} - - for _, refString := range image.RepoTags { - ref, err := reference.ParseNormalizedNamed(refString) - if err != nil { - continue - } - if nt, ok := ref.(reference.NamedTagged); ok { - familiarRef := reference.FamiliarName(ref) - repoTags[familiarRef] = append(repoTags[familiarRef], nt.Tag()) - } - } - for _, refString := range image.RepoDigests { - ref, err := reference.ParseNormalizedNamed(refString) - if err != nil { - continue - } - if c, ok := ref.(reference.Canonical); ok { - familiarRef := reference.FamiliarName(ref) - repoDigests[familiarRef] = append(repoDigests[familiarRef], c.Digest().String()) - } - } - - for repo, tags := range repoTags { - digests := repoDigests[repo] - - // Do not display digests as their own row - delete(repoDigests, repo) - - if !ctx.Digest { - // Ignore digest references, just show tag once - digests = nil - } - - for _, tag := range tags { - if len(digests) == 0 { - images = append(images, &imageContext{ - trunc: ctx.Trunc, - i: image, - repo: repo, - tag: tag, - digest: "", - }) - continue - } - // Display the digests for each tag - for _, dgst := range digests { - images = append(images, &imageContext{ - trunc: ctx.Trunc, - i: image, - repo: repo, - tag: tag, - digest: dgst, - }) - } - - } - } - - // Show rows for remaining digest only references - for repo, digests := range repoDigests { - // If digests are displayed, show row per digest - if ctx.Digest { - for _, dgst := range digests { - images = append(images, &imageContext{ - trunc: ctx.Trunc, - i: image, - repo: repo, - tag: "", - digest: dgst, - }) - } - } else { - images = append(images, &imageContext{ - trunc: ctx.Trunc, - i: image, - repo: repo, - tag: "", - }) - } - } + formatted = imageFormatTaggedAndDigest(ctx, image) } - for _, imageCtx := range images { + for _, imageCtx := range formatted { if err := format(imageCtx); err != nil { return err } @@ -183,6 +107,82 @@ func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subC return nil } +func imageFormatTaggedAndDigest(ctx ImageContext, image types.ImageSummary) []*imageContext { + repoTags := map[string][]string{} + repoDigests := map[string][]string{} + images := []*imageContext{} + + for _, refString := range image.RepoTags { + ref, err := reference.ParseNormalizedNamed(refString) + if err != nil { + continue + } + if nt, ok := ref.(reference.NamedTagged); ok { + familiarRef := reference.FamiliarName(ref) + repoTags[familiarRef] = append(repoTags[familiarRef], nt.Tag()) + } + } + for _, refString := range image.RepoDigests { + ref, err := reference.ParseNormalizedNamed(refString) + if err != nil { + continue + } + if c, ok := ref.(reference.Canonical); ok { + familiarRef := reference.FamiliarName(ref) + repoDigests[familiarRef] = append(repoDigests[familiarRef], c.Digest().String()) + } + } + + addImage := func(repo, tag, digest string) { + image := &imageContext{ + trunc: ctx.Trunc, + i: image, + repo: repo, + tag: tag, + digest: digest, + } + images = append(images, image) + } + + for repo, tags := range repoTags { + digests := repoDigests[repo] + + // Do not display digests as their own row + delete(repoDigests, repo) + + if !needDigest(ctx) { + // Ignore digest references, just show tag once + digests = nil + } + + for _, tag := range tags { + if len(digests) == 0 { + addImage(repo, tag, "") + continue + } + // Display the digests for each tag + for _, dgst := range digests { + addImage(repo, tag, dgst) + } + + } + } + + // Show rows for remaining digest only references + for repo, digests := range repoDigests { + // If digests are displayed, show row per digest + if ctx.Digest { + for _, dgst := range digests { + addImage(repo, "", dgst) + } + } else { + addImage(repo, "", "") + + } + } + return images +} + type imageContext struct { HeaderContext trunc bool diff --git a/components/cli/cli/command/formatter/image_test.go b/components/cli/cli/command/formatter/image_test.go index b3c4cc8094..20b73a52c1 100644 --- a/components/cli/cli/command/formatter/image_test.go +++ b/components/cli/cli/command/formatter/image_test.go @@ -55,6 +55,26 @@ func TestImageContext(t *testing.T) { i: types.ImageSummary{}, digest: "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a", }, "sha256:d149ab53f8718e987c3a3024bb8aa0e2caadf6c0328f1d9d850b2a2a67f2819a", ctx.Digest}, + { + imageContext{ + i: types.ImageSummary{Containers: 10}, + }, "10", ctx.Containers, + }, + { + imageContext{ + i: types.ImageSummary{VirtualSize: 10000}, + }, "10kB", ctx.VirtualSize, + }, + { + imageContext{ + i: types.ImageSummary{SharedSize: 10000}, + }, "10kB", ctx.SharedSize, + }, + { + imageContext{ + i: types.ImageSummary{SharedSize: 5000, VirtualSize: 20000}, + }, "15kB", ctx.UniqueSize, + }, } for _, c := range cases { @@ -62,8 +82,8 @@ func TestImageContext(t *testing.T) { v := c.call() if strings.Contains(v, ",") { compareMultipleValues(t, v, c.expValue) - } else if v != c.expValue { - t.Fatalf("Expected %s, was %s\n", c.expValue, v) + } else { + assert.Equal(t, c.expValue, v) } } } @@ -137,6 +157,14 @@ image }, "REPOSITORY\nimage\nimage\n\n", }, + { + ImageContext{ + Context: Context{ + Format: NewImageFormat("table {{.Digest}}", true, false), + }, + }, + "DIGEST\nsha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf\n\n\n", + }, { ImageContext{ Context: Context{ diff --git a/components/cli/cli/command/formatter/search.go b/components/cli/cli/command/formatter/search.go new file mode 100644 index 0000000000..c19318a831 --- /dev/null +++ b/components/cli/cli/command/formatter/search.go @@ -0,0 +1,104 @@ +package formatter + +import ( + "strconv" + "strings" + + registry "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/pkg/stringutils" +) + +const ( + defaultSearchTableFormat = "table {{.Name}}\t{{.Description}}\t{{.StarCount}}\t{{.IsOfficial}}\t{{.IsAutomated}}" + + starsHeader = "STARS" + officialHeader = "OFFICIAL" + automatedHeader = "AUTOMATED" +) + +// NewSearchFormat returns a Format for rendering using a network Context +func NewSearchFormat(source string) Format { + switch source { + case "": + return defaultSearchTableFormat + case TableFormatKey: + return defaultSearchTableFormat + } + return Format(source) +} + +// SearchWrite writes the context +func SearchWrite(ctx Context, results []registry.SearchResult, auto bool, stars int) error { + render := func(format func(subContext subContext) error) error { + for _, result := range results { + // --automated and -s, --stars are deprecated since Docker 1.12 + if (auto && !result.IsAutomated) || (stars > result.StarCount) { + continue + } + searchCtx := &searchContext{trunc: ctx.Trunc, s: result} + if err := format(searchCtx); err != nil { + return err + } + } + return nil + } + searchCtx := searchContext{} + searchCtx.header = map[string]string{ + "Name": nameHeader, + "Description": descriptionHeader, + "StarCount": starsHeader, + "IsOfficial": officialHeader, + "IsAutomated": automatedHeader, + } + return ctx.Write(&searchCtx, render) +} + +type searchContext struct { + HeaderContext + trunc bool + json bool + s registry.SearchResult +} + +func (c *searchContext) MarshalJSON() ([]byte, error) { + c.json = true + return marshalJSON(c) +} + +func (c *searchContext) Name() string { + return c.s.Name +} + +func (c *searchContext) Description() string { + desc := strings.Replace(c.s.Description, "\n", " ", -1) + desc = strings.Replace(desc, "\r", " ", -1) + if c.trunc { + desc = stringutils.Ellipsis(desc, 45) + } + return desc +} + +func (c *searchContext) StarCount() string { + return strconv.Itoa(c.s.StarCount) +} + +func (c *searchContext) formatBool(value bool) string { + switch { + case value && c.json: + return "true" + case value: + return "[OK]" + case c.json: + return "false" + default: + return "" + } +} + +func (c *searchContext) IsOfficial() string { + return c.formatBool(c.s.IsOfficial) +} + +func (c *searchContext) IsAutomated() string { + return c.formatBool(c.s.IsAutomated) +} diff --git a/components/cli/cli/command/formatter/search_test.go b/components/cli/cli/command/formatter/search_test.go new file mode 100644 index 0000000000..4fa96f8ae8 --- /dev/null +++ b/components/cli/cli/command/formatter/search_test.go @@ -0,0 +1,284 @@ +package formatter + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + + registrytypes "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/pkg/stringutils" + "github.com/stretchr/testify/assert" +) + +func TestSearchContext(t *testing.T) { + name := "nginx" + starCount := 5000 + + var ctx searchContext + cases := []struct { + searchCtx searchContext + expValue string + call func() string + }{ + {searchContext{ + s: registrytypes.SearchResult{Name: name}, + }, name, ctx.Name}, + {searchContext{ + s: registrytypes.SearchResult{StarCount: starCount}, + }, "5000", ctx.StarCount}, + {searchContext{ + s: registrytypes.SearchResult{IsOfficial: true}, + }, "[OK]", ctx.IsOfficial}, + {searchContext{ + s: registrytypes.SearchResult{IsOfficial: false}, + }, "", ctx.IsOfficial}, + {searchContext{ + s: registrytypes.SearchResult{IsAutomated: true}, + }, "[OK]", ctx.IsAutomated}, + {searchContext{ + s: registrytypes.SearchResult{IsAutomated: false}, + }, "", ctx.IsAutomated}, + } + + for _, c := range cases { + ctx = c.searchCtx + v := c.call() + if strings.Contains(v, ",") { + compareMultipleValues(t, v, c.expValue) + } else if v != c.expValue { + t.Fatalf("Expected %s, was %s\n", c.expValue, v) + } + } +} + +func TestSearchContextDescription(t *testing.T) { + shortDescription := "Official build of Nginx." + longDescription := "Automated Nginx reverse proxy for docker containers" + descriptionWReturns := "Automated\nNginx reverse\rproxy\rfor docker\ncontainers" + + var ctx searchContext + cases := []struct { + searchCtx searchContext + expValue string + call func() string + }{ + {searchContext{ + s: registrytypes.SearchResult{Description: shortDescription}, + trunc: true, + }, shortDescription, ctx.Description}, + {searchContext{ + s: registrytypes.SearchResult{Description: shortDescription}, + trunc: false, + }, shortDescription, ctx.Description}, + {searchContext{ + s: registrytypes.SearchResult{Description: longDescription}, + trunc: false, + }, longDescription, ctx.Description}, + {searchContext{ + s: registrytypes.SearchResult{Description: longDescription}, + trunc: true, + }, stringutils.Ellipsis(longDescription, 45), ctx.Description}, + {searchContext{ + s: registrytypes.SearchResult{Description: descriptionWReturns}, + trunc: false, + }, longDescription, ctx.Description}, + {searchContext{ + s: registrytypes.SearchResult{Description: descriptionWReturns}, + trunc: true, + }, stringutils.Ellipsis(longDescription, 45), ctx.Description}, + } + + for _, c := range cases { + ctx = c.searchCtx + v := c.call() + if strings.Contains(v, ",") { + compareMultipleValues(t, v, c.expValue) + } else if v != c.expValue { + t.Fatalf("Expected %s, was %s\n", c.expValue, v) + } + } +} + +func TestSearchContextWrite(t *testing.T) { + cases := []struct { + context Context + expected string + }{ + + // Errors + { + Context{Format: "{{InvalidFunction}}"}, + `Template parsing error: template: :1: function "InvalidFunction" not defined +`, + }, + { + Context{Format: "{{nil}}"}, + `Template parsing error: template: :1:2: executing "" at : nil is not a command +`, + }, + // Table format + { + Context{Format: NewSearchFormat("table")}, + `NAME DESCRIPTION STARS OFFICIAL AUTOMATED +result1 Official build 5000 [OK] +result2 Not official 5 [OK] +`, + }, + { + Context{Format: NewSearchFormat("table {{.Name}}")}, + `NAME +result1 +result2 +`, + }, + // Custom Format + { + Context{Format: NewSearchFormat("{{.Name}}")}, + `result1 +result2 +`, + }, + // Custom Format with CreatedAt + { + Context{Format: NewSearchFormat("{{.Name}} {{.StarCount}}")}, + `result1 5000 +result2 5 +`, + }, + } + + for _, testcase := range cases { + results := []registrytypes.SearchResult{ + {Name: "result1", Description: "Official build", StarCount: 5000, IsOfficial: true, IsAutomated: false}, + {Name: "result2", Description: "Not official", StarCount: 5, IsOfficial: false, IsAutomated: true}, + } + out := bytes.NewBufferString("") + testcase.context.Output = out + err := SearchWrite(testcase.context, results, false, 0) + if err != nil { + assert.Error(t, err, testcase.expected) + } else { + assert.Equal(t, out.String(), testcase.expected) + } + } +} + +func TestSearchContextWriteAutomated(t *testing.T) { + cases := []struct { + context Context + expected string + }{ + + // Table format + { + Context{Format: NewSearchFormat("table")}, + `NAME DESCRIPTION STARS OFFICIAL AUTOMATED +result2 Not official 5 [OK] +`, + }, + { + Context{Format: NewSearchFormat("table {{.Name}}")}, + `NAME +result2 +`, + }, + } + + for _, testcase := range cases { + results := []registrytypes.SearchResult{ + {Name: "result1", Description: "Official build", StarCount: 5000, IsOfficial: true, IsAutomated: false}, + {Name: "result2", Description: "Not official", StarCount: 5, IsOfficial: false, IsAutomated: true}, + } + out := bytes.NewBufferString("") + testcase.context.Output = out + err := SearchWrite(testcase.context, results, true, 0) + if err != nil { + assert.Error(t, err, testcase.expected) + } else { + assert.Equal(t, out.String(), testcase.expected) + } + } +} + +func TestSearchContextWriteStars(t *testing.T) { + cases := []struct { + context Context + expected string + }{ + + // Table format + { + Context{Format: NewSearchFormat("table")}, + `NAME DESCRIPTION STARS OFFICIAL AUTOMATED +result1 Official build 5000 [OK] +`, + }, + { + Context{Format: NewSearchFormat("table {{.Name}}")}, + `NAME +result1 +`, + }, + } + + for _, testcase := range cases { + results := []registrytypes.SearchResult{ + {Name: "result1", Description: "Official build", StarCount: 5000, IsOfficial: true, IsAutomated: false}, + {Name: "result2", Description: "Not official", StarCount: 5, IsOfficial: false, IsAutomated: true}, + } + out := bytes.NewBufferString("") + testcase.context.Output = out + err := SearchWrite(testcase.context, results, false, 6) + if err != nil { + assert.Error(t, err, testcase.expected) + } else { + assert.Equal(t, out.String(), testcase.expected) + } + } +} + +func TestSearchContextWriteJSON(t *testing.T) { + results := []registrytypes.SearchResult{ + {Name: "result1", Description: "Official build", StarCount: 5000, IsOfficial: true, IsAutomated: false}, + {Name: "result2", Description: "Not official", StarCount: 5, IsOfficial: false, IsAutomated: true}, + } + expectedJSONs := []map[string]interface{}{ + {"Name": "result1", "Description": "Official build", "StarCount": "5000", "IsOfficial": "true", "IsAutomated": "false"}, + {"Name": "result2", "Description": "Not official", "StarCount": "5", "IsOfficial": "false", "IsAutomated": "true"}, + } + + out := bytes.NewBufferString("") + err := SearchWrite(Context{Format: "{{json .}}", Output: out}, results, false, 0) + if err != nil { + t.Fatal(err) + } + for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { + t.Logf("Output: line %d: %s", i, line) + var m map[string]interface{} + if err := json.Unmarshal([]byte(line), &m); err != nil { + t.Fatal(err) + } + assert.Equal(t, m, expectedJSONs[i]) + } +} + +func TestSearchContextWriteJSONField(t *testing.T) { + results := []registrytypes.SearchResult{ + {Name: "result1", Description: "Official build", StarCount: 5000, IsOfficial: true, IsAutomated: false}, + {Name: "result2", Description: "Not official", StarCount: 5, IsOfficial: false, IsAutomated: true}, + } + out := bytes.NewBufferString("") + err := SearchWrite(Context{Format: "{{json .Name}}", Output: out}, results, false, 0) + if err != nil { + t.Fatal(err) + } + for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") { + t.Logf("Output: line %d: %s", i, line) + var s string + if err := json.Unmarshal([]byte(line), &s); err != nil { + t.Fatal(err) + } + assert.Equal(t, s, results[i].Name) + } +} diff --git a/components/cli/cli/command/idresolver/idresolver_test.go b/components/cli/cli/command/idresolver/idresolver_test.go index f740c13eff..98bd306d16 100644 --- a/components/cli/cli/command/idresolver/idresolver_test.go +++ b/components/cli/cli/command/idresolver/idresolver_test.go @@ -5,7 +5,7 @@ import ( "github.com/docker/docker/api/types/swarm" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" + . "github.com/docker/cli/internal/test/builders" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "golang.org/x/net/context" diff --git a/components/cli/cli/command/image/build/context_test.go b/components/cli/cli/command/image/build/context_test.go index 8bcbea48dd..a15b535ad7 100644 --- a/components/cli/cli/command/image/build/context_test.go +++ b/components/cli/cli/command/image/build/context_test.go @@ -11,8 +11,8 @@ import ( "strings" "testing" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/components/cli/cli/command/image/build_test.go b/components/cli/cli/command/image/build_test.go index c874e90965..325c82343e 100644 --- a/components/cli/cli/command/image/build_test.go +++ b/components/cli/cli/command/image/build_test.go @@ -10,7 +10,7 @@ import ( "testing" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/archive" "github.com/stretchr/testify/assert" @@ -73,7 +73,7 @@ func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) { // starting with `github.com/` are special-cased, and the build command attempts // to clone the remote repo. func TestRunBuildFromGitHubSpecialCase(t *testing.T) { - cmd := NewBuildCommand(&command.DockerCli{}) + cmd := NewBuildCommand(test.NewFakeCli(nil)) cmd.SetArgs([]string{"github.com/docker/no-such-repository"}) cmd.SetOutput(ioutil.Discard) err := cmd.Execute() diff --git a/components/cli/cli/command/image/history_test.go b/components/cli/cli/command/image/history_test.go index 59c79f573e..eb656e5cf4 100644 --- a/components/cli/cli/command/image/history_test.go +++ b/components/cli/cli/command/image/history_test.go @@ -3,14 +3,13 @@ package image import ( "fmt" "io/ioutil" - "regexp" "testing" "time" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types/image" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -96,11 +95,9 @@ func TestNewHistoryCommandSuccess(t *testing.T) { assert.NoError(t, err) actual := cli.OutBuffer().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) + golden.Assert(t, actual, fmt.Sprintf("history-command-success.%s.golden", tc.name)) } else { - match, _ := regexp.MatchString(tc.outputRegex, actual) - assert.True(t, match) + assert.Regexp(t, tc.outputRegex, actual) } } } diff --git a/components/cli/cli/command/image/import_test.go b/components/cli/cli/command/image/import_test.go index 6fda4d6cf1..7f9bc2d8dc 100644 --- a/components/cli/cli/command/image/import_test.go +++ b/components/cli/cli/command/image/import_test.go @@ -1,15 +1,14 @@ package image import ( - "bytes" "io" "io/ioutil" "strings" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -36,8 +35,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})) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -91,8 +89,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})) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) assert.NoError(t, cmd.Execute()) diff --git a/components/cli/cli/command/image/inspect_test.go b/components/cli/cli/command/image/inspect_test.go index acf80f3782..4e94d06231 100644 --- a/components/cli/cli/command/image/inspect_test.go +++ b/components/cli/cli/command/image/inspect_test.go @@ -1,15 +1,14 @@ package image import ( - "bytes" "fmt" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -26,8 +25,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{})) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -78,15 +76,13 @@ func TestNewInspectCommandSuccess(t *testing.T) { } for _, tc := range testCases { imageInspectInvocationCount = 0 - buf := new(bytes.Buffer) - cmd := newInspectCommand(test.NewFakeCliWithOutput(&fakeClient{imageInspectFunc: tc.imageInspectFunc}, buf)) + cli := test.NewFakeCli(&fakeClient{imageInspectFunc: tc.imageInspectFunc}) + cmd := newInspectCommand(cli) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) err := cmd.Execute() assert.NoError(t, err) - actual := buf.String() - expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("inspect-command-success.%s.golden", tc.name))[:]) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("inspect-command-success.%s.golden", tc.name)) assert.Equal(t, imageInspectInvocationCount, tc.imageCount) } } diff --git a/components/cli/cli/command/image/list_test.go b/components/cli/cli/command/image/list_test.go index 5ae1e47e8d..1755bb3e3d 100644 --- a/components/cli/cli/command/image/list_test.go +++ b/components/cli/cli/command/image/list_test.go @@ -1,16 +1,15 @@ package image import ( - "bytes" "fmt" "io/ioutil" "testing" "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -80,17 +79,14 @@ func TestNewImagesCommandSuccess(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{imageListFunc: tc.imageListFunc}, buf) + cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}) cli.SetConfigFile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat}) cmd := NewImagesCommand(cli) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) err := cmd.Execute() assert.NoError(t, err) - actual := buf.String() - expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("list-command-success.%s.golden", tc.name))[:]) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("list-command-success.%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/image/load_test.go b/components/cli/cli/command/image/load_test.go index 519604d096..5f05bca47b 100644 --- a/components/cli/cli/command/image/load_test.go +++ b/components/cli/cli/command/image/load_test.go @@ -1,17 +1,16 @@ package image import ( - "bytes" "fmt" "io" "io/ioutil" "strings" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -92,14 +91,12 @@ func TestNewLoadCommandSuccess(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := NewLoadCommand(test.NewFakeCliWithOutput(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, buf)) + cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}) + cmd := NewLoadCommand(cli) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) err := cmd.Execute() assert.NoError(t, err) - actual := buf.String() - expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("load-command-success.%s.golden", tc.name))[:]) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("load-command-success.%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/image/prune_test.go b/components/cli/cli/command/image/prune_test.go index 7f320c7a94..12f51b60fe 100644 --- a/components/cli/cli/command/image/prune_test.go +++ b/components/cli/cli/command/image/prune_test.go @@ -1,16 +1,15 @@ package image import ( - "bytes" "fmt" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -37,10 +36,9 @@ 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) cmd.SetArgs(tc.args) testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -85,16 +83,12 @@ func TestNewPruneCommandSuccess(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := NewPruneCommand(test.NewFakeCliWithOutput(&fakeClient{ - imagesPruneFunc: tc.imagesPruneFunc, - }, buf)) + cli := test.NewFakeCli(&fakeClient{imagesPruneFunc: tc.imagesPruneFunc}) + cmd := NewPruneCommand(cli) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) err := cmd.Execute() assert.NoError(t, err) - actual := buf.String() - expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("prune-command-success.%s.golden", tc.name))[:]) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("prune-command-success.%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/image/pull_test.go b/components/cli/cli/command/image/pull_test.go index 71d258323a..157b391fdd 100644 --- a/components/cli/cli/command/image/pull_test.go +++ b/components/cli/cli/command/image/pull_test.go @@ -5,9 +5,9 @@ import ( "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -68,8 +68,6 @@ func TestNewPullCommandSuccess(t *testing.T) { cmd.SetArgs(tc.args) err := cmd.Execute() assert.NoError(t, err) - actual := cli.OutBuffer().String() - expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("pull-command-success.%s.golden", tc.name))[:]) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("pull-command-success.%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/image/push_test.go b/components/cli/cli/command/image/push_test.go index 1ead5b1549..48d78b7d03 100644 --- a/components/cli/cli/command/image/push_test.go +++ b/components/cli/cli/command/image/push_test.go @@ -6,9 +6,9 @@ import ( "strings" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) diff --git a/components/cli/cli/command/image/remove_test.go b/components/cli/cli/command/image/remove_test.go index f88bc88520..e89e6ddaf8 100644 --- a/components/cli/cli/command/image/remove_test.go +++ b/components/cli/cli/command/image/remove_test.go @@ -5,10 +5,10 @@ import ( "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -96,16 +96,14 @@ func TestNewRemoveCommandSuccess(t *testing.T) { }, } for _, tc := range testCases { - fakeCli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc}) - cmd := NewRemoveCommand(fakeCli) + cli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc}) + cmd := NewRemoveCommand(cli) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) assert.NoError(t, cmd.Execute()) if tc.expectedErrMsg != "" { - assert.Equal(t, tc.expectedErrMsg, fakeCli.ErrBuffer().String()) + assert.Equal(t, tc.expectedErrMsg, cli.ErrBuffer().String()) } - actual := fakeCli.OutBuffer().String() - expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("remove-command-success.%s.golden", tc.name))[:]) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("remove-command-success.%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/image/save_test.go b/components/cli/cli/command/image/save_test.go index 84dee142d8..f402da7e84 100644 --- a/components/cli/cli/command/image/save_test.go +++ b/components/cli/cli/command/image/save_test.go @@ -1,15 +1,14 @@ package image import ( - "bytes" "io" "io/ioutil" "os" "strings" "testing" - "github.com/docker/cli/cli/internal/test" - "github.com/docker/docker/pkg/testutil" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -90,11 +89,11 @@ 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 }, - }, new(bytes.Buffer))) + })) cmd.SetOutput(ioutil.Discard) cmd.SetArgs(tc.args) assert.NoError(t, cmd.Execute()) diff --git a/components/cli/cli/command/image/tag_test.go b/components/cli/cli/command/image/tag_test.go index c86bd22b36..0698522969 100644 --- a/components/cli/cli/command/image/tag_test.go +++ b/components/cli/cli/command/image/tag_test.go @@ -4,8 +4,8 @@ import ( "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" - "github.com/docker/docker/pkg/testutil" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/stretchr/testify/assert" ) diff --git a/components/cli/cli/command/network/connect_test.go b/components/cli/cli/command/network/connect_test.go index 577dd8c25a..064aa041c8 100644 --- a/components/cli/cli/command/network/connect_test.go +++ b/components/cli/cli/command/network/connect_test.go @@ -4,9 +4,9 @@ import ( "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types/network" - "github.com/docker/docker/pkg/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "golang.org/x/net/context" diff --git a/components/cli/cli/command/network/create_test.go b/components/cli/cli/command/network/create_test.go index 76c760d0ad..dda68046db 100644 --- a/components/cli/cli/command/network/create_test.go +++ b/components/cli/cli/command/network/create_test.go @@ -5,10 +5,10 @@ import ( "strings" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "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" diff --git a/components/cli/cli/command/network/disconnect_test.go b/components/cli/cli/command/network/disconnect_test.go index 9b915a19b5..e3920e9777 100644 --- a/components/cli/cli/command/network/disconnect_test.go +++ b/components/cli/cli/command/network/disconnect_test.go @@ -4,8 +4,8 @@ import ( "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" - "github.com/docker/docker/pkg/testutil" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/pkg/errors" "golang.org/x/net/context" ) diff --git a/components/cli/cli/command/node/demote_test.go b/components/cli/cli/command/node/demote_test.go index d786cd47a2..bf86e5227e 100644 --- a/components/cli/cli/command/node/demote_test.go +++ b/components/cli/cli/command/node/demote_test.go @@ -4,12 +4,12 @@ import ( "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" "github.com/stretchr/testify/assert" ) diff --git a/components/cli/cli/command/node/inspect_test.go b/components/cli/cli/command/node/inspect_test.go index ca88371f8a..739a4783be 100644 --- a/components/cli/cli/command/node/inspect_test.go +++ b/components/cli/cli/command/node/inspect_test.go @@ -1,19 +1,18 @@ package node import ( - "bytes" "fmt" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -67,12 +66,11 @@ 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)) + })) cmd.SetArgs(tc.args) for key, value := range tc.flags { cmd.Flags().Set(key, value) @@ -109,16 +107,13 @@ func TestNodeInspectPretty(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newInspectCommand( - test.NewFakeCliWithOutput(&fakeClient{ - nodeInspectFunc: tc.nodeInspectFunc, - }, buf)) + cli := test.NewFakeCli(&fakeClient{ + nodeInspectFunc: tc.nodeInspectFunc, + }) + cmd := newInspectCommand(cli) cmd.SetArgs([]string{"nodeID"}) cmd.Flags().Set("pretty", "true") assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("node-inspect-pretty.%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("node-inspect-pretty.%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/node/list_test.go b/components/cli/cli/command/node/list_test.go index f579ebc88a..b3a42df460 100644 --- a/components/cli/cli/command/node/list_test.go +++ b/components/cli/cli/command/node/list_test.go @@ -1,19 +1,17 @@ package node import ( - "bytes" "io/ioutil" "testing" "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/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/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" + . "github.com/docker/cli/internal/test/builders" "github.com/stretchr/testify/assert" ) @@ -58,9 +56,9 @@ func TestNodeList(t *testing.T) { cli := test.NewFakeCli(&fakeClient{ nodeListFunc: func() ([]swarm.Node, error) { return []swarm.Node{ - *Node(NodeID("nodeID1"), Hostname("nodeHostname1"), Manager(Leader())), - *Node(NodeID("nodeID2"), Hostname("nodeHostname2"), Manager()), - *Node(NodeID("nodeID3"), Hostname("nodeHostname3")), + *Node(NodeID("nodeID1"), Hostname("node-2-foo"), Manager(Leader())), + *Node(NodeID("nodeID2"), Hostname("node-10-foo"), Manager()), + *Node(NodeID("nodeID3"), Hostname("node-1-foo")), }, nil }, infoFunc: func() (types.Info, error) { @@ -74,39 +72,25 @@ func TestNodeList(t *testing.T) { 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`) + golden.Assert(t, cli.OutBuffer().String(), "node-list-sort.golden") } 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(), + *Node(NodeID("nodeID1")), }, nil }, - }, buf) + }) cmd := newListCommand(cli) cmd.Flags().Set("quiet", "true") assert.NoError(t, cmd.Execute()) - assert.Contains(t, buf.String(), "nodeID") + assert.Equal(t, cli.OutBuffer().String(), "nodeID1\n") } -// Test case for #24090 -func TestNodeListContainsHostname(t *testing.T) { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{}, buf) - cmd := newListCommand(cli) - assert.NoError(t, cmd.Execute()) - assert.Contains(t, buf.String(), "HOSTNAME") -} - -func TestNodeListDefaultFormat(t *testing.T) { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ +func TestNodeListDefaultFormatFromConfig(t *testing.T) { + cli := test.NewFakeCli(&fakeClient{ nodeListFunc: func() ([]swarm.Node, error) { return []swarm.Node{ *Node(NodeID("nodeID1"), Hostname("nodeHostname1"), Manager(Leader())), @@ -121,20 +105,17 @@ func TestNodeListDefaultFormat(t *testing.T) { }, }, nil }, - }, buf) + }) cli.SetConfigFile(&configfile.ConfigFile{ NodesFormat: "{{.ID}}: {{.Hostname}} {{.Status}}/{{.ManagerStatus}}", }) cmd := newListCommand(cli) assert.NoError(t, cmd.Execute()) - assert.Contains(t, buf.String(), `nodeID1: nodeHostname1 Ready/Leader`) - assert.Contains(t, buf.String(), `nodeID2: nodeHostname2 Ready/Reachable`) - assert.Contains(t, buf.String(), `nodeID3: nodeHostname3 Ready`) + golden.Assert(t, cli.OutBuffer().String(), "node-list-format-from-config.golden") } 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())), @@ -148,32 +129,12 @@ func TestNodeListFormat(t *testing.T) { }, }, nil }, - }, buf) + }) cli.SetConfigFile(&configfile.ConfigFile{ NodesFormat: "{{.ID}}: {{.Hostname}} {{.Status}}/{{.ManagerStatus}}", }) cmd := newListCommand(cli) cmd.Flags().Set("format", "{{.Hostname}}: {{.ManagerStatus}}") assert.NoError(t, cmd.Execute()) - 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)) + golden.Assert(t, cli.OutBuffer().String(), "node-list-format-flag.golden") } diff --git a/components/cli/cli/command/node/promote_test.go b/components/cli/cli/command/node/promote_test.go index 95e067eb15..4c66342346 100644 --- a/components/cli/cli/command/node/promote_test.go +++ b/components/cli/cli/command/node/promote_test.go @@ -1,16 +1,15 @@ package node import ( - "bytes" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" "github.com/stretchr/testify/assert" ) @@ -40,12 +39,11 @@ 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)) + })) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -53,9 +51,8 @@ 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 }, @@ -65,15 +62,14 @@ func TestNodePromoteNoChange(t *testing.T) { } return nil }, - }, buf)) + })) cmd.SetArgs([]string{"nodeID"}) assert.NoError(t, cmd.Execute()) } 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 }, @@ -83,7 +79,7 @@ func TestNodePromoteMultipleNode(t *testing.T) { } return nil }, - }, buf)) + })) cmd.SetArgs([]string{"nodeID1", "nodeID2"}) assert.NoError(t, cmd.Execute()) } diff --git a/components/cli/cli/command/node/ps_test.go b/components/cli/cli/command/node/ps_test.go index d3ef6f30a2..836a130f76 100644 --- a/components/cli/cli/command/node/ps_test.go +++ b/components/cli/cli/command/node/ps_test.go @@ -1,20 +1,18 @@ package node import ( - "bytes" "fmt" "io/ioutil" "testing" "time" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + . "github.com/docker/cli/internal/test/builders" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -50,14 +48,13 @@ func TestNodePsErrors(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newPsCommand( - test.NewFakeCliWithOutput(&fakeClient{ - infoFunc: tc.infoFunc, - nodeInspectFunc: tc.nodeInspectFunc, - taskInspectFunc: tc.taskInspectFunc, - taskListFunc: tc.taskListFunc, - }, buf)) + cli := test.NewFakeCli(&fakeClient{ + infoFunc: tc.infoFunc, + nodeInspectFunc: tc.nodeInspectFunc, + taskInspectFunc: tc.taskInspectFunc, + taskListFunc: tc.taskListFunc, + }) + cmd := newPsCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { cmd.Flags().Set(key, value) @@ -114,21 +111,18 @@ func TestNodePs(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newPsCommand( - test.NewFakeCliWithOutput(&fakeClient{ - infoFunc: tc.infoFunc, - nodeInspectFunc: tc.nodeInspectFunc, - taskInspectFunc: tc.taskInspectFunc, - taskListFunc: tc.taskListFunc, - }, buf)) + cli := test.NewFakeCli(&fakeClient{ + infoFunc: tc.infoFunc, + nodeInspectFunc: tc.nodeInspectFunc, + taskInspectFunc: tc.taskInspectFunc, + taskListFunc: tc.taskListFunc, + }) + cmd := newPsCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { cmd.Flags().Set(key, value) } assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("node-ps.%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("node-ps.%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/node/remove_test.go b/components/cli/cli/command/node/remove_test.go index 81daa2f025..78fc4f76fa 100644 --- a/components/cli/cli/command/node/remove_test.go +++ b/components/cli/cli/command/node/remove_test.go @@ -1,12 +1,11 @@ package node import ( - "bytes" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" - "github.com/docker/docker/pkg/testutil" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -29,11 +28,10 @@ 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) cmd.SetOutput(ioutil.Discard) testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -41,8 +39,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{})) cmd.SetArgs([]string{"nodeID1", "nodeID2"}) assert.NoError(t, cmd.Execute()) } diff --git a/components/cli/cli/command/node/testdata/node-inspect-pretty.manager-leader.golden b/components/cli/cli/command/node/testdata/node-inspect-pretty.manager-leader.golden index 461fc46ea2..5cd95c5b91 100644 --- a/components/cli/cli/command/node/testdata/node-inspect-pretty.manager-leader.golden +++ b/components/cli/cli/command/node/testdata/node-inspect-pretty.manager-leader.golden @@ -1,10 +1,10 @@ ID: nodeID Name: defaultNodeName -Hostname: defaultNodeHostname -Joined at: 2009-11-10 23:00:00 +0000 utc +Hostname: defaultNodeHostname +Joined at: 2009-11-10 23:00:00 +0000 utc Status: State: Ready - Availability: Active + Availability: Active Address: 127.0.0.1 Manager Status: Address: 127.0.0.1 @@ -15,11 +15,10 @@ Platform: Architecture: x86_64 Resources: CPUs: 0 - Memory: 20 MiB + Memory: 20MiB Plugins: - Network: bridge, overlay - Volume: local + Network: bridge, overlay + Volume: local Engine Version: 1.13.0 Engine Labels: - - engine = label - + - engine=label diff --git a/components/cli/cli/command/node/testdata/node-inspect-pretty.manager.golden b/components/cli/cli/command/node/testdata/node-inspect-pretty.manager.golden index 2c660188d5..a63718293c 100644 --- a/components/cli/cli/command/node/testdata/node-inspect-pretty.manager.golden +++ b/components/cli/cli/command/node/testdata/node-inspect-pretty.manager.golden @@ -1,10 +1,10 @@ ID: nodeID Name: defaultNodeName -Hostname: defaultNodeHostname -Joined at: 2009-11-10 23:00:00 +0000 utc +Hostname: defaultNodeHostname +Joined at: 2009-11-10 23:00:00 +0000 utc Status: State: Ready - Availability: Active + Availability: Active Address: 127.0.0.1 Manager Status: Address: 127.0.0.1 @@ -15,11 +15,10 @@ Platform: Architecture: x86_64 Resources: CPUs: 0 - Memory: 20 MiB + Memory: 20MiB Plugins: - Network: bridge, overlay - Volume: local + Network: bridge, overlay + Volume: local Engine Version: 1.13.0 Engine Labels: - - engine = label - + - engine=label diff --git a/components/cli/cli/command/node/testdata/node-inspect-pretty.simple.golden b/components/cli/cli/command/node/testdata/node-inspect-pretty.simple.golden index e63bc12596..8aaf90899e 100644 --- a/components/cli/cli/command/node/testdata/node-inspect-pretty.simple.golden +++ b/components/cli/cli/command/node/testdata/node-inspect-pretty.simple.golden @@ -1,23 +1,22 @@ ID: nodeID Name: defaultNodeName Labels: - - lbl1 = value1 -Hostname: defaultNodeHostname -Joined at: 2009-11-10 23:00:00 +0000 utc + - lbl1=value1 +Hostname: defaultNodeHostname +Joined at: 2009-11-10 23:00:00 +0000 utc Status: State: Ready - Availability: Active + Availability: Active Address: 127.0.0.1 Platform: Operating System: linux Architecture: x86_64 Resources: CPUs: 0 - Memory: 20 MiB + Memory: 20MiB Plugins: - Network: bridge, overlay - Volume: local + Network: bridge, overlay + Volume: local Engine Version: 1.13.0 Engine Labels: - - engine = label - + - engine=label diff --git a/components/cli/cli/command/node/testdata/node-list-format-flag.golden b/components/cli/cli/command/node/testdata/node-list-format-flag.golden new file mode 100644 index 0000000000..c898df13e6 --- /dev/null +++ b/components/cli/cli/command/node/testdata/node-list-format-flag.golden @@ -0,0 +1,2 @@ +nodeHostname1: Leader +nodeHostname2: Reachable diff --git a/components/cli/cli/command/node/testdata/node-list-format-from-config.golden b/components/cli/cli/command/node/testdata/node-list-format-from-config.golden new file mode 100644 index 0000000000..91beb4a29b --- /dev/null +++ b/components/cli/cli/command/node/testdata/node-list-format-from-config.golden @@ -0,0 +1,3 @@ +nodeID1: nodeHostname1 Ready/Leader +nodeID2: nodeHostname2 Ready/Reachable +nodeID3: nodeHostname3 Ready/ diff --git a/components/cli/cli/command/node/testdata/node-list-sort.golden b/components/cli/cli/command/node/testdata/node-list-sort.golden index e2f2811994..ad8c34a813 100644 --- a/components/cli/cli/command/node/testdata/node-list-sort.golden +++ b/components/cli/cli/command/node/testdata/node-list-sort.golden @@ -1,3 +1,4 @@ -node-1-foo: -node-2-foo: Leader -node-10-foo: Reachable +ID HOSTNAME STATUS AVAILABILITY MANAGER STATUS +nodeID3 node-1-foo Ready Active +nodeID1 * node-2-foo Ready Active Leader +nodeID2 node-10-foo Ready Active Reachable diff --git a/components/cli/cli/command/node/testdata/node-ps.simple.golden b/components/cli/cli/command/node/testdata/node-ps.simple.golden index f9555d8792..b1818b96b4 100644 --- a/components/cli/cli/command/node/testdata/node-ps.simple.golden +++ b/components/cli/cli/command/node/testdata/node-ps.simple.golden @@ -1,2 +1,2 @@ -ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS -taskID rl02d5gwz6chzu7il5fhtb8be.1 myimage:mytag defaultNodeName Ready Ready 2 hours ago *:80->80/tcp +ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS +taskID rl02d5gwz6chzu7il5fhtb8be.1 myimage:mytag defaultNodeName Ready Ready 2 hours ago *:80->80/tcp diff --git a/components/cli/cli/command/node/testdata/node-ps.with-errors.golden b/components/cli/cli/command/node/testdata/node-ps.with-errors.golden index 273b30fa11..99e34931a6 100644 --- a/components/cli/cli/command/node/testdata/node-ps.with-errors.golden +++ b/components/cli/cli/command/node/testdata/node-ps.with-errors.golden @@ -1,4 +1,4 @@ -ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS -taskID1 failure.1 myimage:mytag defaultNodeName Ready Ready 2 hours ago "a task error" -taskID2 \_ failure.1 myimage:mytag defaultNodeName Ready Ready 3 hours ago "a task error" -taskID3 \_ failure.1 myimage:mytag defaultNodeName Ready Ready 4 hours ago "a task error" +ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS +taskID1 failure.1 myimage:mytag defaultNodeName Ready Ready 2 hours ago "a task error" +taskID2 \_ failure.1 myimage:mytag defaultNodeName Ready Ready 3 hours ago "a task error" +taskID3 \_ failure.1 myimage:mytag defaultNodeName Ready Ready 4 hours ago "a task error" diff --git a/components/cli/cli/command/node/update_test.go b/components/cli/cli/command/node/update_test.go index 3abb720c1c..e1aa28a806 100644 --- a/components/cli/cli/command/node/update_test.go +++ b/components/cli/cli/command/node/update_test.go @@ -4,12 +4,12 @@ import ( "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" "github.com/stretchr/testify/assert" ) diff --git a/components/cli/cli/command/registry/search.go b/components/cli/cli/command/registry/search.go index 443c9a3fa8..49ac0f43cf 100644 --- a/components/cli/cli/command/registry/search.go +++ b/components/cli/cli/command/registry/search.go @@ -1,23 +1,21 @@ package registry import ( - "fmt" "sort" - "strings" - "text/tabwriter" "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" registrytypes "github.com/docker/docker/api/types/registry" - "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/registry" "github.com/spf13/cobra" "golang.org/x/net/context" ) type searchOptions struct { + format string term string noTrunc bool limit int @@ -47,6 +45,7 @@ func NewSearchCommand(dockerCli command.Cli) *cobra.Command { flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output") flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided") flags.IntVar(&options.limit, "limit", registry.DefaultSearchLimit, "Max number of search results") + flags.StringVar(&options.format, "format", "", "Pretty-print search using a Go template") flags.BoolVar(&options.automated, "automated", false, "Only show automated builds") flags.UintVarP(&options.stars, "stars", "s", 0, "Only displays with at least x stars") @@ -89,32 +88,12 @@ func runSearch(dockerCli command.Cli, options searchOptions) error { results := searchResultsByStars(unorderedResults) sort.Sort(results) - - w := tabwriter.NewWriter(dockerCli.Out(), 10, 1, 3, ' ', 0) - fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") - for _, res := range results { - // --automated and -s, --stars are deprecated since Docker 1.12 - if (options.automated && !res.IsAutomated) || (int(options.stars) > res.StarCount) { - continue - } - desc := strings.Replace(res.Description, "\n", " ", -1) - desc = strings.Replace(desc, "\r", " ", -1) - if !options.noTrunc { - desc = stringutils.Ellipsis(desc, 45) - } - fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount) - if res.IsOfficial { - fmt.Fprint(w, "[OK]") - - } - fmt.Fprint(w, "\t") - if res.IsAutomated { - fmt.Fprint(w, "[OK]") - } - fmt.Fprint(w, "\n") + searchCtx := formatter.Context{ + Output: dockerCli.Out(), + Format: formatter.NewSearchFormat(options.format), + Trunc: !options.noTrunc, } - w.Flush() - return nil + return formatter.SearchWrite(searchCtx, results, options.automated, int(options.stars)) } // searchResultsByStars sorts search results in descending order by number of stars. diff --git a/components/cli/cli/command/registry_test.go b/components/cli/cli/command/registry_test.go index 5e52e470a1..3f3d5f579f 100644 --- a/components/cli/cli/command/registry_test.go +++ b/components/cli/cli/command/registry_test.go @@ -7,9 +7,9 @@ import ( "github.com/stretchr/testify/assert" "golang.org/x/net/context" - // Prevents a circular import with "github.com/docker/cli/cli/internal/test" + // Prevents a circular import with "github.com/docker/cli/internal/test" . "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/client" ) diff --git a/components/cli/cli/command/secret/create_test.go b/components/cli/cli/command/secret/create_test.go index 592ede0fe5..f9f70cfedd 100644 --- a/components/cli/cli/command/secret/create_test.go +++ b/components/cli/cli/command/secret/create_test.go @@ -1,18 +1,17 @@ package secret import ( - "bytes" "io/ioutil" "path/filepath" "reflect" "strings" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "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/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -54,9 +53,8 @@ func TestSecretCreateErrors(t *testing.T) { func TestSecretCreateWithName(t *testing.T) { name := "foo" - buf := new(bytes.Buffer) var actual []byte - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ secretCreateFunc: func(spec swarm.SecretSpec) (types.SecretCreateResponse, error) { if spec.Name != name { return types.SecretCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name) @@ -68,14 +66,13 @@ func TestSecretCreateWithName(t *testing.T) { ID: "ID-" + spec.Name, }, nil }, - }, buf) + }) cmd := newSecretCreateCommand(cli) cmd.SetArgs([]string{name, filepath.Join("testdata", secretDataFile)}) assert.NoError(t, cmd.Execute()) - expected := golden.Get(t, actual, secretDataFile) - assert.Equal(t, expected, actual) - assert.Equal(t, "ID-"+name, strings.TrimSpace(buf.String())) + golden.Assert(t, string(actual), secretDataFile) + assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String())) } func TestSecretCreateWithLabels(t *testing.T) { @@ -85,8 +82,7 @@ func TestSecretCreateWithLabels(t *testing.T) { } name := "foo" - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ secretCreateFunc: func(spec swarm.SecretSpec) (types.SecretCreateResponse, error) { if spec.Name != name { return types.SecretCreateResponse{}, errors.Errorf("expected name %q, got %q", name, spec.Name) @@ -100,12 +96,12 @@ func TestSecretCreateWithLabels(t *testing.T) { ID: "ID-" + spec.Name, }, nil }, - }, buf) + }) cmd := newSecretCreateCommand(cli) cmd.SetArgs([]string{name, filepath.Join("testdata", secretDataFile)}) 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(buf.String())) + assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String())) } diff --git a/components/cli/cli/command/secret/inspect_test.go b/components/cli/cli/command/secret/inspect_test.go index 093624ddfe..188f6a7c4c 100644 --- a/components/cli/cli/command/secret/inspect_test.go +++ b/components/cli/cli/command/secret/inspect_test.go @@ -1,19 +1,18 @@ package secret import ( - "bytes" "fmt" "io/ioutil" "testing" "time" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -53,11 +52,10 @@ func TestSecretInspectErrors(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) cmd := newSecretInspectCommand( - test.NewFakeCliWithOutput(&fakeClient{ + test.NewFakeCli(&fakeClient{ secretInspectFunc: tc.secretInspectFunc, - }, buf), + }), ) cmd.SetArgs(tc.args) for key, value := range tc.flags { @@ -95,17 +93,13 @@ func TestSecretInspectWithoutFormat(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newSecretInspectCommand( - test.NewFakeCliWithOutput(&fakeClient{ - secretInspectFunc: tc.secretInspectFunc, - }, buf), - ) + cli := test.NewFakeCli(&fakeClient{ + secretInspectFunc: tc.secretInspectFunc, + }) + cmd := newSecretInspectCommand(cli) cmd.SetArgs(tc.args) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("secret-inspect-without-format.%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("secret-inspect-without-format.%s.golden", tc.name)) } } @@ -135,18 +129,14 @@ func TestSecretInspectWithFormat(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newSecretInspectCommand( - test.NewFakeCliWithOutput(&fakeClient{ - secretInspectFunc: tc.secretInspectFunc, - }, buf), - ) + cli := test.NewFakeCli(&fakeClient{ + secretInspectFunc: tc.secretInspectFunc, + }) + cmd := newSecretInspectCommand(cli) cmd.SetArgs(tc.args) cmd.Flags().Set("format", tc.format) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("secret-inspect-with-format.%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("secret-inspect-with-format.%s.golden", tc.name)) } } @@ -171,16 +161,13 @@ func TestSecretInspectPretty(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newSecretInspectCommand( - test.NewFakeCliWithOutput(&fakeClient{ - secretInspectFunc: tc.secretInspectFunc, - }, buf)) + cli := test.NewFakeCli(&fakeClient{ + secretInspectFunc: tc.secretInspectFunc, + }) + cmd := newSecretInspectCommand(cli) cmd.SetArgs([]string{"secretID"}) cmd.Flags().Set("pretty", "true") assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("secret-inspect-pretty.%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("secret-inspect-pretty.%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/secret/ls_test.go b/components/cli/cli/command/secret/ls_test.go index fb033efea6..2509e24dc3 100644 --- a/components/cli/cli/command/secret/ls_test.go +++ b/components/cli/cli/command/secret/ls_test.go @@ -7,14 +7,14 @@ import ( "time" "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -36,11 +36,10 @@ func TestSecretListErrors(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) cmd := newSecretListCommand( - test.NewFakeCliWithOutput(&fakeClient{ + test.NewFakeCli(&fakeClient{ secretListFunc: tc.secretListFunc, - }, buf), + }), ) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) @@ -50,7 +49,7 @@ func TestSecretListErrors(t *testing.T) { func TestSecretList(t *testing.T) { buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) { return []swarm.Secret{ *Secret(SecretID("ID-foo"), @@ -67,18 +66,15 @@ func TestSecretList(t *testing.T) { ), }, nil }, - }, buf) + }) cmd := newSecretListCommand(cli) cmd.SetOutput(buf) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "secret-list.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "secret-list.golden") } func TestSecretListWithQuietOption(t *testing.T) { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) { return []swarm.Secret{ *Secret(SecretID("ID-foo"), SecretName("foo")), @@ -87,18 +83,15 @@ func TestSecretListWithQuietOption(t *testing.T) { })), }, nil }, - }, buf) + }) cmd := newSecretListCommand(cli) cmd.Flags().Set("quiet", "true") assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "secret-list-with-quiet-option.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "secret-list-with-quiet-option.golden") } func TestSecretListWithConfigFormat(t *testing.T) { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) { return []swarm.Secret{ *Secret(SecretID("ID-foo"), SecretName("foo")), @@ -107,20 +100,17 @@ func TestSecretListWithConfigFormat(t *testing.T) { })), }, nil }, - }, buf) + }) cli.SetConfigFile(&configfile.ConfigFile{ SecretFormat: "{{ .Name }} {{ .Labels }}", }) cmd := newSecretListCommand(cli) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "secret-list-with-config-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "secret-list-with-config-format.golden") } func TestSecretListWithFormat(t *testing.T) { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) { return []swarm.Secret{ *Secret(SecretID("ID-foo"), SecretName("foo")), @@ -129,18 +119,15 @@ func TestSecretListWithFormat(t *testing.T) { })), }, nil }, - }, buf) + }) cmd := newSecretListCommand(cli) cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}") assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "secret-list-with-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "secret-list-with-format.golden") } func TestSecretListWithFilter(t *testing.T) { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ secretListFunc: func(options types.SecretListOptions) ([]swarm.Secret, error) { assert.Equal(t, "foo", options.Filters.Get("name")[0], "foo") assert.Equal(t, "lbl1=Label-bar", options.Filters.Get("label")[0]) @@ -159,12 +146,10 @@ func TestSecretListWithFilter(t *testing.T) { ), }, nil }, - }, buf) + }) cmd := newSecretListCommand(cli) cmd.Flags().Set("filter", "name=foo") cmd.Flags().Set("filter", "label=lbl1=Label-bar") assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "secret-list-with-filter.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "secret-list-with-filter.golden") } diff --git a/components/cli/cli/command/secret/remove_test.go b/components/cli/cli/command/secret/remove_test.go index ad479c0a80..accb3ea042 100644 --- a/components/cli/cli/command/secret/remove_test.go +++ b/components/cli/cli/command/secret/remove_test.go @@ -1,13 +1,12 @@ package secret import ( - "bytes" "io/ioutil" "strings" "testing" - "github.com/docker/cli/cli/internal/test" - "github.com/docker/docker/pkg/testutil" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -31,11 +30,10 @@ func TestSecretRemoveErrors(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) cmd := newSecretRemoveCommand( - test.NewFakeCliWithOutput(&fakeClient{ + test.NewFakeCli(&fakeClient{ secretRemoveFunc: tc.secretRemoveFunc, - }, buf), + }), ) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) @@ -45,27 +43,25 @@ func TestSecretRemoveErrors(t *testing.T) { func TestSecretRemoveWithName(t *testing.T) { names := []string{"foo", "bar"} - buf := new(bytes.Buffer) var removedSecrets []string - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ secretRemoveFunc: func(name string) error { removedSecrets = append(removedSecrets, name) return nil }, - }, buf) + }) cmd := newSecretRemoveCommand(cli) cmd.SetArgs(names) assert.NoError(t, cmd.Execute()) - assert.Equal(t, names, strings.Split(strings.TrimSpace(buf.String()), "\n")) + assert.Equal(t, names, strings.Split(strings.TrimSpace(cli.OutBuffer().String()), "\n")) assert.Equal(t, names, removedSecrets) } func TestSecretRemoveContinueAfterError(t *testing.T) { names := []string{"foo", "bar"} - buf := new(bytes.Buffer) var removedSecrets []string - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ secretRemoveFunc: func(name string) error { removedSecrets = append(removedSecrets, name) if name == "foo" { @@ -73,7 +69,7 @@ func TestSecretRemoveContinueAfterError(t *testing.T) { } return nil }, - }, buf) + }) cmd := newSecretRemoveCommand(cli) cmd.SetOutput(ioutil.Discard) diff --git a/components/cli/cli/command/secret/testdata/secret-inspect-pretty.simple.golden b/components/cli/cli/command/secret/testdata/secret-inspect-pretty.simple.golden index cc14091e8b..4e2df1fb4c 100644 --- a/components/cli/cli/command/secret/testdata/secret-inspect-pretty.simple.golden +++ b/components/cli/cli/command/secret/testdata/secret-inspect-pretty.simple.golden @@ -1,6 +1,6 @@ -ID: secretID -Name: secretName +ID: secretID +Name: secretName Labels: - - lbl1=value1 -Created at: 0001-01-01 00:00:00+0000 utc -Updated at: 0001-01-01 00:00:00+0000 utc + - lbl1=value1 +Created at: 0001-01-01 00:00:00 +0000 utc +Updated at: 0001-01-01 00:00:00 +0000 utc diff --git a/components/cli/cli/command/secret/testdata/secret-inspect-without-format.multiple-secrets-with-labels.golden b/components/cli/cli/command/secret/testdata/secret-inspect-without-format.multiple-secrets-with-labels.golden index 6887c185f1..b01a400c57 100644 --- a/components/cli/cli/command/secret/testdata/secret-inspect-without-format.multiple-secrets-with-labels.golden +++ b/components/cli/cli/command/secret/testdata/secret-inspect-without-format.multiple-secrets-with-labels.golden @@ -1,7 +1,7 @@ [ { "ID": "ID-foo", - "Version": {}, + "Version": {}, "CreatedAt": "0001-01-01T00:00:00Z", "UpdatedAt": "0001-01-01T00:00:00Z", "Spec": { @@ -13,7 +13,7 @@ }, { "ID": "ID-bar", - "Version": {}, + "Version": {}, "CreatedAt": "0001-01-01T00:00:00Z", "UpdatedAt": "0001-01-01T00:00:00Z", "Spec": { diff --git a/components/cli/cli/command/secret/testdata/secret-inspect-without-format.single-secret.golden b/components/cli/cli/command/secret/testdata/secret-inspect-without-format.single-secret.golden index ea42ec6f4f..c4f41c1067 100644 --- a/components/cli/cli/command/secret/testdata/secret-inspect-without-format.single-secret.golden +++ b/components/cli/cli/command/secret/testdata/secret-inspect-without-format.single-secret.golden @@ -1,9 +1,9 @@ [ { "ID": "ID-foo", - "Version": {}, - "CreatedAt": "0001-01-01T00:00:00Z", - "UpdatedAt": "0001-01-01T00:00:00Z", + "Version": {}, + "CreatedAt": "0001-01-01T00:00:00Z", + "UpdatedAt": "0001-01-01T00:00:00Z", "Spec": { "Name": "foo", "Labels": null diff --git a/components/cli/cli/command/secret/testdata/secret-list-with-config-format.golden b/components/cli/cli/command/secret/testdata/secret-list-with-config-format.golden index 9a47538804..11c39229b8 100644 --- a/components/cli/cli/command/secret/testdata/secret-list-with-config-format.golden +++ b/components/cli/cli/command/secret/testdata/secret-list-with-config-format.golden @@ -1,2 +1,2 @@ -foo +foo bar label=label-bar diff --git a/components/cli/cli/command/secret/testdata/secret-list-with-format.golden b/components/cli/cli/command/secret/testdata/secret-list-with-format.golden index 9a47538804..11c39229b8 100644 --- a/components/cli/cli/command/secret/testdata/secret-list-with-format.golden +++ b/components/cli/cli/command/secret/testdata/secret-list-with-format.golden @@ -1,2 +1,2 @@ -foo +foo bar label=label-bar diff --git a/components/cli/cli/command/service/client_test.go b/components/cli/cli/command/service/client_test.go index b4f8905159..69c76951f7 100644 --- a/components/cli/cli/command/service/client_test.go +++ b/components/cli/cli/command/service/client_test.go @@ -5,17 +5,19 @@ import ( "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" "golang.org/x/net/context" + // Import builders to get the builder function as package function + . "github.com/docker/cli/internal/test/builders" ) type fakeClient struct { client.Client - serviceListFunc func(context.Context, types.ServiceListOptions) ([]swarm.Service, error) + serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) + serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) + serviceListFunc func(context.Context, types.ServiceListOptions) ([]swarm.Service, error) + infoFunc func(ctx context.Context) (types.Info, error) } -func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { - if f.serviceListFunc != nil { - return f.serviceListFunc(ctx, options) - } +func (f *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) { return nil, nil } @@ -23,10 +25,37 @@ func (f *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions return nil, nil } -func (f *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) { +func (f *fakeClient) ServiceInspectWithRaw(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { + if f.serviceInspectWithRawFunc != nil { + return f.serviceInspectWithRawFunc(ctx, serviceID, options) + } + + return *Service(ServiceID(serviceID)), []byte{}, nil +} + +func (f *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) { + if f.serviceListFunc != nil { + return f.serviceListFunc(ctx, options) + } + return nil, nil } +func (f *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) { + if f.serviceUpdateFunc != nil { + return f.serviceUpdateFunc(ctx, serviceID, version, service, options) + } + + return types.ServiceUpdateResponse{}, nil +} + +func (f *fakeClient) Info(ctx context.Context) (types.Info, error) { + if f.infoFunc == nil { + return types.Info{}, nil + } + return f.infoFunc(ctx) +} + func newService(id string, name string) swarm.Service { return swarm.Service{ ID: id, diff --git a/components/cli/cli/command/service/cmd.go b/components/cli/cli/command/service/cmd.go index 94e3a97d4e..107abf32d0 100644 --- a/components/cli/cli/command/service/cmd.go +++ b/components/cli/cli/command/service/cmd.go @@ -26,6 +26,7 @@ func NewServiceCommand(dockerCli *command.DockerCli) *cobra.Command { newScaleCommand(dockerCli), newUpdateCommand(dockerCli), newLogsCommand(dockerCli), + newRollbackCommand(dockerCli), ) return cmd } diff --git a/components/cli/cli/command/service/create.go b/components/cli/cli/command/service/create.go index e4b42a45ce..ecf7a37e28 100644 --- a/components/cli/cli/command/service/create.go +++ b/components/cli/cli/command/service/create.go @@ -12,7 +12,7 @@ import ( "golang.org/x/net/context" ) -func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { +func newCreateCommand(dockerCli command.Cli) *cobra.Command { opts := newServiceOptions() cmd := &cobra.Command{ @@ -62,7 +62,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { return cmd } -func runCreate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *serviceOptions) error { +func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *serviceOptions) error { apiClient := dockerCli.Client() createOpts := types.ServiceCreateOptions{} diff --git a/components/cli/cli/command/service/helpers.go b/components/cli/cli/command/service/helpers.go index 8fb82c7cca..de6878c17b 100644 --- a/components/cli/cli/command/service/helpers.go +++ b/components/cli/cli/command/service/helpers.go @@ -15,7 +15,7 @@ import ( // waitOnService waits for the service to converge. It outputs a progress bar, // if appropriate based on the CLI flags. -func waitOnService(ctx context.Context, dockerCli *command.DockerCli, serviceID string, quiet bool) error { +func waitOnService(ctx context.Context, dockerCli command.Cli, serviceID string, quiet bool) error { errChan := make(chan error, 1) pipeReader, pipeWriter := io.Pipe() diff --git a/components/cli/cli/command/service/inspect.go b/components/cli/cli/command/service/inspect.go index ea0009d3a5..dbe8ac8ddc 100644 --- a/components/cli/cli/command/service/inspect.go +++ b/components/cli/cli/command/service/inspect.go @@ -20,7 +20,7 @@ type inspectOptions struct { pretty bool } -func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command { +func newInspectCommand(dockerCli command.Cli) *cobra.Command { var opts inspectOptions cmd := &cobra.Command{ @@ -43,7 +43,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command { return cmd } -func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error { +func runInspect(dockerCli command.Cli, opts inspectOptions) error { client := dockerCli.Client() ctx := context.Background() diff --git a/components/cli/cli/command/service/list_test.go b/components/cli/cli/command/service/list_test.go index 85ccd1fb79..679c733701 100644 --- a/components/cli/cli/command/service/list_test.go +++ b/components/cli/cli/command/service/list_test.go @@ -3,14 +3,12 @@ package service import ( "testing" - "golang.org/x/net/context" - - "github.com/docker/cli/cli/internal/test" + "github.com/docker/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/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" + "golang.org/x/net/context" ) func TestServiceListOrder(t *testing.T) { @@ -26,7 +24,5 @@ func TestServiceListOrder(t *testing.T) { cmd := newListCommand(cli) cmd.Flags().Set("format", "{{.Name}}") assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "service-list-sort.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "service-list-sort.golden") } diff --git a/components/cli/cli/command/service/ps.go b/components/cli/cli/command/service/ps.go index 07dbba7230..e441f9e1aa 100644 --- a/components/cli/cli/command/service/ps.go +++ b/components/cli/cli/command/service/ps.go @@ -56,6 +56,9 @@ func runPS(dockerCli command.Cli, options psOptions) error { if err != nil { return err } + if err := updateNodeFilter(ctx, client, filter); err != nil { + return err + } tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter}) if err != nil { @@ -130,16 +133,20 @@ loop: if serviceCount == 0 { return filter, nil, errors.New(strings.Join(notfound, "\n")) } + return filter, notfound, err +} + +func updateNodeFilter(ctx context.Context, client client.APIClient, filter filters.Args) error { if filter.Include("node") { nodeFilters := filter.Get("node") for _, nodeFilter := range nodeFilters { nodeReference, err := node.Reference(ctx, client, nodeFilter) if err != nil { - return filter, nil, err + return err } filter.Del("node", nodeFilter) filter.Add("node", nodeReference) } } - return filter, notfound, err + return nil } diff --git a/components/cli/cli/command/service/ps_test.go b/components/cli/cli/command/service/ps_test.go index dac79ab50a..be26870284 100644 --- a/components/cli/cli/command/service/ps_test.go +++ b/components/cli/cli/command/service/ps_test.go @@ -3,9 +3,7 @@ package service import ( "testing" - "bytes" - - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/cli/opts" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" @@ -82,8 +80,7 @@ func TestRunPSWarnsOnNotFound(t *testing.T) { }, } - out := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(client, out) + cli := test.NewFakeCli(client) options := psOptions{ services: []string{"foo", "bar"}, filter: opts.NewFilterOpt(), @@ -92,3 +89,25 @@ func TestRunPSWarnsOnNotFound(t *testing.T) { err := runPS(cli, options) assert.EqualError(t, err, "no such service: bar") } + +func TestUpdateNodeFilter(t *testing.T) { + selfNodeID := "foofoo" + filter := filters.NewArgs() + filter.Add("node", "one") + filter.Add("node", "two") + filter.Add("node", "self") + + client := &fakeClient{ + infoFunc: func(_ context.Context) (types.Info, error) { + return types.Info{Swarm: swarm.Info{NodeID: selfNodeID}}, nil + }, + } + + updateNodeFilter(context.Background(), client, filter) + + expected := filters.NewArgs() + expected.Add("node", "one") + expected.Add("node", "two") + expected.Add("node", selfNodeID) + assert.Equal(t, expected, filter) +} diff --git a/components/cli/cli/command/service/remove.go b/components/cli/cli/command/service/remove.go index bc6a202f64..38833b2e7e 100644 --- a/components/cli/cli/command/service/remove.go +++ b/components/cli/cli/command/service/remove.go @@ -11,7 +11,7 @@ import ( "golang.org/x/net/context" ) -func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command { +func newRemoveCommand(dockerCli command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "rm SERVICE [SERVICE...]", @@ -27,7 +27,7 @@ func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command { return cmd } -func runRemove(dockerCli *command.DockerCli, sids []string) error { +func runRemove(dockerCli command.Cli, sids []string) error { client := dockerCli.Client() ctx := context.Background() diff --git a/components/cli/cli/command/service/rollback.go b/components/cli/cli/command/service/rollback.go new file mode 100644 index 0000000000..5cef656255 --- /dev/null +++ b/components/cli/cli/command/service/rollback.go @@ -0,0 +1,65 @@ +package service + +import ( + "context" + "fmt" + + "github.com/docker/cli/cli" + "github.com/docker/cli/cli/command" + "github.com/docker/docker/api/types" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +func newRollbackCommand(dockerCli command.Cli) *cobra.Command { + options := newServiceOptions() + + cmd := &cobra.Command{ + Use: "rollback [OPTIONS] SERVICE", + Short: "Revert changes to a service's configuration", + Args: cli.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return runRollback(dockerCli, cmd.Flags(), options, args[0]) + }, + Tags: map[string]string{"version": "1.31"}, + } + + flags := cmd.Flags() + flags.BoolVarP(&options.quiet, flagQuiet, "q", false, "Suppress progress output") + addDetachFlag(flags, &options.detach) + + return cmd +} + +func runRollback(dockerCli command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error { + apiClient := dockerCli.Client() + ctx := context.Background() + + service, _, err := apiClient.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{}) + if err != nil { + return err + } + + spec := &service.Spec + updateOpts := types.ServiceUpdateOptions{ + Rollback: "previous", + } + + response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, *spec, updateOpts) + if err != nil { + return err + } + + for _, warning := range response.Warnings { + fmt.Fprintln(dockerCli.Err(), warning) + } + + fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID) + + if options.detach { + warnDetachDefault(dockerCli.Err(), apiClient.ClientVersion(), flags, "rolled back") + return nil + } + + return waitOnService(ctx, dockerCli, serviceID, options.quiet) +} diff --git a/components/cli/cli/command/service/rollback_test.go b/components/cli/cli/command/service/rollback_test.go new file mode 100644 index 0000000000..c063676645 --- /dev/null +++ b/components/cli/cli/command/service/rollback_test.go @@ -0,0 +1,104 @@ +package service + +import ( + "fmt" + "io/ioutil" + "strings" + "testing" + + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" + "github.com/stretchr/testify/assert" + "golang.org/x/net/context" +) + +func TestRollback(t *testing.T) { + testCases := []struct { + name string + args []string + serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) + expectedDockerCliErr string + }{ + { + name: "rollback-service", + args: []string{"service-id"}, + }, + { + name: "rollback-service-with-warnings", + args: []string{"service-id"}, + serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) { + response := types.ServiceUpdateResponse{} + + response.Warnings = []string{ + "- warning 1", + "- warning 2", + } + + return response, nil + }, + expectedDockerCliErr: "- warning 1\n- warning 2", + }, + } + + for _, tc := range testCases { + cli := test.NewFakeCli(&fakeClient{ + serviceUpdateFunc: tc.serviceUpdateFunc, + }) + cmd := newRollbackCommand(cli) + cmd.SetArgs(tc.args) + cmd.Flags().Set("quiet", "true") + cmd.SetOutput(ioutil.Discard) + assert.NoError(t, cmd.Execute()) + assert.Equal(t, strings.TrimSpace(cli.ErrBuffer().String()), tc.expectedDockerCliErr) + } +} + +func TestRollbackWithErrors(t *testing.T) { + testCases := []struct { + name string + args []string + serviceInspectWithRawFunc func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) + serviceUpdateFunc func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) + expectedError string + }{ + { + name: "not-enough-args", + expectedError: "requires exactly 1 argument", + }, + { + name: "too-many-args", + args: []string{"service-id-1", "service-id-2"}, + expectedError: "requires exactly 1 argument", + }, + { + name: "service-does-not-exists", + args: []string{"service-id"}, + serviceInspectWithRawFunc: func(ctx context.Context, serviceID string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { + return swarm.Service{}, []byte{}, fmt.Errorf("no such services: %s", serviceID) + }, + expectedError: "no such services: service-id", + }, + { + name: "service-update-failed", + args: []string{"service-id"}, + serviceUpdateFunc: func(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) { + return types.ServiceUpdateResponse{}, fmt.Errorf("no such services: %s", serviceID) + }, + expectedError: "no such services: service-id", + }, + } + + for _, tc := range testCases { + cmd := newRollbackCommand( + test.NewFakeCli(&fakeClient{ + serviceInspectWithRawFunc: tc.serviceInspectWithRawFunc, + serviceUpdateFunc: tc.serviceUpdateFunc, + })) + cmd.SetArgs(tc.args) + cmd.Flags().Set("quiet", "true") + cmd.SetOutput(ioutil.Discard) + testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) + } +} diff --git a/components/cli/cli/command/service/scale.go b/components/cli/cli/command/service/scale.go index f58786156a..4910d4fa50 100644 --- a/components/cli/cli/command/service/scale.go +++ b/components/cli/cli/command/service/scale.go @@ -19,7 +19,7 @@ type scaleOptions struct { detach bool } -func newScaleCommand(dockerCli *command.DockerCli) *cobra.Command { +func newScaleCommand(dockerCli command.Cli) *cobra.Command { options := &scaleOptions{} cmd := &cobra.Command{ @@ -54,7 +54,7 @@ func scaleArgs(cmd *cobra.Command, args []string) error { return nil } -func runScale(dockerCli *command.DockerCli, flags *pflag.FlagSet, options *scaleOptions, args []string) error { +func runScale(dockerCli command.Cli, flags *pflag.FlagSet, options *scaleOptions, args []string) error { var errs []string var serviceIDs []string ctx := context.Background() @@ -96,7 +96,7 @@ func runScale(dockerCli *command.DockerCli, flags *pflag.FlagSet, options *scale return errors.Errorf(strings.Join(errs, "\n")) } -func runServiceScale(ctx context.Context, dockerCli *command.DockerCli, serviceID string, scale uint64) error { +func runServiceScale(ctx context.Context, dockerCli command.Cli, serviceID string, scale uint64) error { client := dockerCli.Client() service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{}) diff --git a/components/cli/cli/command/service/update.go b/components/cli/cli/command/service/update.go index dba05a1b1e..90057adedf 100644 --- a/components/cli/cli/command/service/update.go +++ b/components/cli/cli/command/service/update.go @@ -22,7 +22,7 @@ import ( "golang.org/x/net/context" ) -func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command { +func newUpdateCommand(dockerCli command.Cli) *cobra.Command { options := newServiceOptions() cmd := &cobra.Command{ @@ -103,7 +103,7 @@ func newListOptsVar() *opts.ListOpts { } // nolint: gocyclo -func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error { +func runUpdate(dockerCli command.Cli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error { apiClient := dockerCli.Client() ctx := context.Background() diff --git a/components/cli/cli/command/stack/client_test.go b/components/cli/cli/command/stack/client_test.go index d1d85193e7..bcb92db6c9 100644 --- a/components/cli/cli/command/stack/client_test.go +++ b/components/cli/cli/command/stack/client_test.go @@ -34,10 +34,13 @@ type fakeClient struct { nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error) taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error) nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error) - serviceRemoveFunc func(serviceID string) error - networkRemoveFunc func(networkID string) error - secretRemoveFunc func(secretID string) error - configRemoveFunc func(configID string) error + + serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) + + serviceRemoveFunc func(serviceID string) error + networkRemoveFunc func(networkID string) error + secretRemoveFunc func(secretID string) error + configRemoveFunc func(configID string) error } func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) { @@ -132,6 +135,14 @@ func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swar return swarm.Node{}, nil, nil } +func (cli *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) { + if cli.serviceUpdateFunc != nil { + return cli.serviceUpdateFunc(serviceID, version, service, options) + } + + return types.ServiceUpdateResponse{}, nil +} + func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error { if cli.serviceRemoveFunc != nil { return cli.serviceRemoveFunc(serviceID) diff --git a/components/cli/cli/command/stack/deploy_composefile.go b/components/cli/cli/command/stack/deploy_composefile.go index 1f50ae1a1d..e511a6f127 100644 --- a/components/cli/cli/command/stack/deploy_composefile.go +++ b/components/cli/cli/command/stack/deploy_composefile.go @@ -2,6 +2,7 @@ package stack import ( "fmt" + "io" "io/ioutil" "os" "path/filepath" @@ -22,7 +23,7 @@ import ( ) func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOptions) error { - configDetails, err := getConfigDetails(opts.composefile) + configDetails, err := getConfigDetails(opts.composefile, dockerCli.In()) if err != nil { return err } @@ -118,16 +119,24 @@ func propertyWarnings(properties map[string]string) string { return strings.Join(msgs, "\n\n") } -func getConfigDetails(composefile string) (composetypes.ConfigDetails, error) { +func getConfigDetails(composefile string, stdin io.Reader) (composetypes.ConfigDetails, error) { var details composetypes.ConfigDetails - absPath, err := filepath.Abs(composefile) - if err != nil { - return details, err + if composefile == "-" { + workingDir, err := os.Getwd() + if err != nil { + return details, err + } + details.WorkingDir = workingDir + } else { + absPath, err := filepath.Abs(composefile) + if err != nil { + return details, err + } + details.WorkingDir = filepath.Dir(absPath) } - details.WorkingDir = filepath.Dir(absPath) - configFile, err := getConfigFile(composefile) + configFile, err := getConfigFile(composefile, stdin) if err != nil { return details, err } @@ -150,15 +159,24 @@ func buildEnvironment(env []string) (map[string]string, error) { return result, nil } -func getConfigFile(filename string) (*composetypes.ConfigFile, error) { - bytes, err := ioutil.ReadFile(filename) +func getConfigFile(filename string, stdin io.Reader) (*composetypes.ConfigFile, error) { + var bytes []byte + var err error + + if filename == "-" { + bytes, err = ioutil.ReadAll(stdin) + } else { + bytes, err = ioutil.ReadFile(filename) + } if err != nil { return nil, err } + config, err := loader.ParseYAML(bytes) if err != nil { return nil, err } + return &composetypes.ConfigFile{ Filename: filename, Config: config, @@ -318,10 +336,17 @@ func deployServices( updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth} - if resolveImage == resolveImageAlways || (resolveImage == resolveImageChanged && image != service.Spec.Labels[convert.LabelImage]) { + switch { + case resolveImage == resolveImageAlways || (resolveImage == resolveImageChanged && image != service.Spec.Labels[convert.LabelImage]): + // image should be updated by the server using QueryRegistry updateOpts.QueryRegistry = true + case image == service.Spec.Labels[convert.LabelImage]: + // image has not changed; update the serviceSpec with the + // existing information that was set by QueryRegistry on the + // previous deploy. Otherwise this will trigger an incorrect + // service update. + serviceSpec.TaskTemplate.ContainerSpec.Image = service.Spec.TaskTemplate.ContainerSpec.Image } - response, err := apiClient.ServiceUpdate( ctx, service.ID, diff --git a/components/cli/cli/command/stack/deploy_composefile_test.go b/components/cli/cli/command/stack/deploy_composefile_test.go index 5d59ff7028..684e688c03 100644 --- a/components/cli/cli/command/stack/deploy_composefile_test.go +++ b/components/cli/cli/command/stack/deploy_composefile_test.go @@ -3,12 +3,13 @@ package stack import ( "os" "path/filepath" + "strings" "testing" - "github.com/docker/cli/cli/internal/test/network" + "github.com/docker/cli/internal/test/network" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/tempfile" + "github.com/gotestyourself/gotestyourself/fs" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -22,13 +23,31 @@ services: foo: image: alpine:3.5 ` - file := tempfile.NewTempFile(t, "test-get-config-details", content) + file := fs.NewFile(t, "test-get-config-details", fs.WithContent(content)) defer file.Remove() - details, err := getConfigDetails(file.Name()) + details, err := getConfigDetails(file.Path(), nil) require.NoError(t, err) - assert.Equal(t, filepath.Dir(file.Name()), details.WorkingDir) - assert.Len(t, details.ConfigFiles, 1) + assert.Equal(t, filepath.Dir(file.Path()), details.WorkingDir) + require.Len(t, details.ConfigFiles, 1) + assert.Equal(t, "3.0", details.ConfigFiles[0].Config["version"]) + assert.Len(t, details.Environment, len(os.Environ())) +} + +func TestGetConfigDetailsStdin(t *testing.T) { + content := ` +version: "3.0" +services: + foo: + image: alpine:3.5 +` + details, err := getConfigDetails("-", strings.NewReader(content)) + require.NoError(t, err) + cwd, err := os.Getwd() + require.NoError(t, err) + assert.Equal(t, cwd, details.WorkingDir) + require.Len(t, details.ConfigFiles, 1) + assert.Equal(t, "3.0", details.ConfigFiles[0].Config["version"]) assert.Len(t, details.Environment, len(os.Environ())) } diff --git a/components/cli/cli/command/stack/deploy_test.go b/components/cli/cli/command/stack/deploy_test.go index c04f86c251..f10fecd1af 100644 --- a/components/cli/cli/command/stack/deploy_test.go +++ b/components/cli/cli/command/stack/deploy_test.go @@ -4,7 +4,9 @@ import ( "testing" "github.com/docker/cli/cli/compose/convert" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" "github.com/stretchr/testify/assert" "golang.org/x/net/context" ) @@ -22,3 +24,80 @@ func TestPruneServices(t *testing.T) { pruneServices(ctx, dockerCli, namespace, services) assert.Equal(t, buildObjectIDs([]string{objectName("foo", "remove")}), client.removedServices) } + +// TestServiceUpdateResolveImageChanged tests that the service's +// image digest is preserved if the image did not change in the compose file +func TestServiceUpdateResolveImageChanged(t *testing.T) { + namespace := convert.NewNamespace("mystack") + + var ( + receivedOptions types.ServiceUpdateOptions + receivedService swarm.ServiceSpec + ) + + client := test.NewFakeCli(&fakeClient{ + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return []swarm.Service{ + { + Spec: swarm.ServiceSpec{ + Annotations: swarm.Annotations{ + Name: namespace.Name() + "_myservice", + Labels: map[string]string{"com.docker.stack.image": "foobar:1.2.3"}, + }, + TaskTemplate: swarm.TaskSpec{ + ContainerSpec: swarm.ContainerSpec{ + Image: "foobar:1.2.3@sha256:deadbeef", + }, + }, + }, + }, + }, nil + }, + serviceUpdateFunc: func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) { + receivedOptions = options + receivedService = service + return types.ServiceUpdateResponse{}, nil + }, + }) + + var testcases = []struct { + image string + expectedQueryRegistry bool + expectedImage string + }{ + // Image not changed + { + image: "foobar:1.2.3", + expectedQueryRegistry: false, + expectedImage: "foobar:1.2.3@sha256:deadbeef", + }, + // Image changed + { + image: "foobar:1.2.4", + expectedQueryRegistry: true, + expectedImage: "foobar:1.2.4", + }, + } + + ctx := context.Background() + + for _, testcase := range testcases { + t.Logf("Testing image %q", testcase.image) + spec := map[string]swarm.ServiceSpec{ + "myservice": { + TaskTemplate: swarm.TaskSpec{ + ContainerSpec: swarm.ContainerSpec{ + Image: testcase.image, + }, + }, + }, + } + err := deployServices(ctx, client, spec, namespace, false, resolveImageChanged) + assert.NoError(t, err) + assert.Equal(t, testcase.expectedQueryRegistry, receivedOptions.QueryRegistry) + assert.Equal(t, testcase.expectedImage, receivedService.TaskTemplate.ContainerSpec.Image) + + receivedService = swarm.ServiceSpec{} + receivedOptions = types.ServiceUpdateOptions{} + } +} diff --git a/components/cli/cli/command/stack/list_test.go b/components/cli/cli/command/stack/list_test.go index 48b141653f..90d32e0f99 100644 --- a/components/cli/cli/command/stack/list_test.go +++ b/components/cli/cli/command/stack/list_test.go @@ -1,17 +1,16 @@ package stack import ( - "bytes" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -61,8 +60,7 @@ func TestListErrors(t *testing.T) { } func TestListWithFormat(t *testing.T) { - buf := new(bytes.Buffer) - cmd := newListCommand(test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { return []swarm.Service{ *Service( @@ -71,17 +69,15 @@ func TestListWithFormat(t *testing.T) { }), )}, nil }, - }, buf)) + }) + cmd := newListCommand(cli) cmd.Flags().Set("format", "{{ .Name }}") assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "stack-list-with-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-list-with-format.golden") } func TestListWithoutFormat(t *testing.T) { - buf := new(bytes.Buffer) - cmd := newListCommand(test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { return []swarm.Service{ *Service( @@ -90,11 +86,10 @@ func TestListWithoutFormat(t *testing.T) { }), )}, nil }, - }, buf)) + }) + cmd := newListCommand(cli) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "stack-list-without-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-list-without-format.golden") } func TestListOrder(t *testing.T) { @@ -140,15 +135,13 @@ func TestListOrder(t *testing.T) { } for _, uc := range usecases { - buf := new(bytes.Buffer) - cmd := newListCommand(test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { return uc.swarmServices, nil }, - }, buf)) + }) + cmd := newListCommand(cli) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), uc.golden) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), uc.golden) } } diff --git a/components/cli/cli/command/stack/ps_test.go b/components/cli/cli/command/stack/ps_test.go index 5387dd0347..282e5e01da 100644 --- a/components/cli/cli/command/stack/ps_test.go +++ b/components/cli/cli/command/stack/ps_test.go @@ -6,13 +6,13 @@ import ( "time" "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -75,9 +75,7 @@ func TestStackPsWithQuietOption(t *testing.T) { cmd.SetArgs([]string{"foo"}) cmd.Flags().Set("quiet", "true") assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "stack-ps-with-quiet-option.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-quiet-option.golden") } @@ -92,9 +90,7 @@ func TestStackPsWithNoTruncOption(t *testing.T) { cmd.Flags().Set("no-trunc", "true") cmd.Flags().Set("format", "{{ .ID }}") assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "stack-ps-with-no-trunc-option.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-no-trunc-option.golden") } func TestStackPsWithNoResolveOption(t *testing.T) { @@ -113,9 +109,7 @@ func TestStackPsWithNoResolveOption(t *testing.T) { cmd.Flags().Set("no-resolve", "true") cmd.Flags().Set("format", "{{ .Node }}") assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "stack-ps-with-no-resolve-option.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-no-resolve-option.golden") } func TestStackPsWithFormat(t *testing.T) { @@ -128,9 +122,7 @@ func TestStackPsWithFormat(t *testing.T) { cmd.SetArgs([]string{"foo"}) cmd.Flags().Set("format", "{{ .Name }}") assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "stack-ps-with-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-format.golden") } func TestStackPsWithConfigFormat(t *testing.T) { @@ -145,9 +137,7 @@ func TestStackPsWithConfigFormat(t *testing.T) { cmd := newPsCommand(cli) cmd.SetArgs([]string{"foo"}) assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "stack-ps-with-config-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-ps-with-config-format.golden") } func TestStackPsWithoutFormat(t *testing.T) { @@ -169,7 +159,5 @@ func TestStackPsWithoutFormat(t *testing.T) { cmd := newPsCommand(cli) cmd.SetArgs([]string{"foo"}) assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "stack-ps-without-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-ps-without-format.golden") } diff --git a/components/cli/cli/command/stack/remove.go b/components/cli/cli/command/stack/remove.go index d95171aabf..157f71ea12 100644 --- a/components/cli/cli/command/stack/remove.go +++ b/components/cli/cli/command/stack/remove.go @@ -2,6 +2,7 @@ package stack import ( "fmt" + "sort" "strings" "github.com/docker/cli/cli" @@ -88,12 +89,19 @@ func runRemove(dockerCli command.Cli, opts removeOptions) error { return nil } +func sortServiceByName(services []swarm.Service) func(i, j int) bool { + return func(i, j int) bool { + return services[i].Spec.Name < services[j].Spec.Name + } +} + func removeServices( ctx context.Context, dockerCli command.Cli, services []swarm.Service, ) bool { var hasError bool + sort.Slice(services, sortServiceByName(services)) for _, service := range services { fmt.Fprintf(dockerCli.Err(), "Removing service %s\n", service.Spec.Name) if err := dockerCli.Client().ServiceRemove(ctx, service.ID); err != nil { diff --git a/components/cli/cli/command/stack/remove_test.go b/components/cli/cli/command/stack/remove_test.go index 88ad8a07d3..c44977c0c6 100644 --- a/components/cli/cli/command/stack/remove_test.go +++ b/components/cli/cli/command/stack/remove_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/stretchr/testify/assert" ) diff --git a/components/cli/cli/command/stack/services_test.go b/components/cli/cli/command/stack/services_test.go index 822660fbc2..2f6d6e0e00 100644 --- a/components/cli/cli/command/stack/services_test.go +++ b/components/cli/cli/command/stack/services_test.go @@ -5,13 +5,13 @@ import ( "testing" "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -103,9 +103,7 @@ func TestStackServicesWithQuietOption(t *testing.T) { cmd.Flags().Set("quiet", "true") cmd.SetArgs([]string{"foo"}) assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "stack-services-with-quiet-option.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-services-with-quiet-option.golden") } func TestStackServicesWithFormat(t *testing.T) { @@ -120,9 +118,7 @@ func TestStackServicesWithFormat(t *testing.T) { cmd.SetArgs([]string{"foo"}) cmd.Flags().Set("format", "{{ .Name }}") assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "stack-services-with-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-services-with-format.golden") } func TestStackServicesWithConfigFormat(t *testing.T) { @@ -139,9 +135,7 @@ func TestStackServicesWithConfigFormat(t *testing.T) { cmd := newServicesCommand(cli) cmd.SetArgs([]string{"foo"}) assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "stack-services-with-config-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-services-with-config-format.golden") } func TestStackServicesWithoutFormat(t *testing.T) { @@ -164,7 +158,5 @@ func TestStackServicesWithoutFormat(t *testing.T) { cmd := newServicesCommand(cli) cmd.SetArgs([]string{"foo"}) assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), "stack-services-without-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "stack-services-without-format.golden") } diff --git a/components/cli/cli/command/stack/testdata/stack-list-sort-natural.golden b/components/cli/cli/command/stack/testdata/stack-list-sort-natural.golden index 09507fbf40..71eb63fd3b 100644 --- a/components/cli/cli/command/stack/testdata/stack-list-sort-natural.golden +++ b/components/cli/cli/command/stack/testdata/stack-list-sort-natural.golden @@ -1,4 +1,4 @@ -NAME SERVICES -service-name-1-foo 1 -service-name-2-foo 1 -service-name-10-foo 1 +NAME SERVICES +service-name-1-foo 1 +service-name-2-foo 1 +service-name-10-foo 1 diff --git a/components/cli/cli/command/stack/testdata/stack-ps-without-format.golden b/components/cli/cli/command/stack/testdata/stack-ps-without-format.golden index 9ca75f5890..ceb4f8411c 100644 --- a/components/cli/cli/command/stack/testdata/stack-ps-without-format.golden +++ b/components/cli/cli/command/stack/testdata/stack-ps-without-format.golden @@ -1,2 +1,2 @@ -ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS -id-foo service-id-foo.1 myimage:mytag node-name-bar Ready Failed 2 hours ago +ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS +id-foo service-id-foo.1 myimage:mytag node-name-bar Ready Failed 2 hours ago diff --git a/components/cli/cli/command/swarm/ca_test.go b/components/cli/cli/command/swarm/ca_test.go index a1a77d59c2..cb2668293a 100644 --- a/components/cli/cli/command/swarm/ca_test.go +++ b/components/cli/cli/command/swarm/ca_test.go @@ -7,9 +7,9 @@ import ( "testing" "time" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/pkg/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/components/cli/cli/command/swarm/init_test.go b/components/cli/cli/command/swarm/init_test.go index f6969d1b0e..24a1b90717 100644 --- a/components/cli/cli/command/swarm/init_test.go +++ b/components/cli/cli/command/swarm/init_test.go @@ -1,16 +1,14 @@ package swarm import ( - "bytes" "fmt" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/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/gotestyourself/gotestyourself/golden" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -65,14 +63,13 @@ func TestSwarmInitErrorOnAPIFailure(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) cmd := newInitCommand( - test.NewFakeCliWithOutput(&fakeClient{ + test.NewFakeCli(&fakeClient{ swarmInitFunc: tc.swarmInitFunc, swarmInspectFunc: tc.swarmInspectFunc, swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc, nodeInspectFunc: tc.nodeInspectFunc, - }, buf)) + })) for key, value := range tc.flags { cmd.Flags().Set(key, value) } @@ -112,20 +109,17 @@ func TestSwarmInit(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newInitCommand( - test.NewFakeCliWithOutput(&fakeClient{ - swarmInitFunc: tc.swarmInitFunc, - swarmInspectFunc: tc.swarmInspectFunc, - swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc, - nodeInspectFunc: tc.nodeInspectFunc, - }, buf)) + cli := test.NewFakeCli(&fakeClient{ + swarmInitFunc: tc.swarmInitFunc, + swarmInspectFunc: tc.swarmInspectFunc, + swarmGetUnlockKeyFunc: tc.swarmGetUnlockKeyFunc, + nodeInspectFunc: tc.nodeInspectFunc, + }) + cmd := newInitCommand(cli) for key, value := range tc.flags { cmd.Flags().Set(key, value) } assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("init-%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("init-%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/swarm/join_test.go b/components/cli/cli/command/swarm/join_test.go index 331f2c1753..69546cd8cf 100644 --- a/components/cli/cli/command/swarm/join_test.go +++ b/components/cli/cli/command/swarm/join_test.go @@ -1,15 +1,14 @@ package swarm import ( - "bytes" "io/ioutil" "strings" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/pkg/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -49,12 +48,11 @@ func TestSwarmJoinErrors(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) cmd := newJoinCommand( - test.NewFakeCliWithOutput(&fakeClient{ + test.NewFakeCli(&fakeClient{ swarmJoinFunc: tc.swarmJoinFunc, infoFunc: tc.infoFunc, - }, buf)) + })) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -91,13 +89,12 @@ func TestSwarmJoin(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newJoinCommand( - test.NewFakeCliWithOutput(&fakeClient{ - infoFunc: tc.infoFunc, - }, buf)) + cli := test.NewFakeCli(&fakeClient{ + infoFunc: tc.infoFunc, + }) + cmd := newJoinCommand(cli) cmd.SetArgs([]string{"remote"}) assert.NoError(t, cmd.Execute()) - assert.Equal(t, strings.TrimSpace(buf.String()), tc.expected) + assert.Equal(t, strings.TrimSpace(cli.OutBuffer().String()), tc.expected) } } diff --git a/components/cli/cli/command/swarm/join_token_test.go b/components/cli/cli/command/swarm/join_token_test.go index f8619a4bd6..6ab503113a 100644 --- a/components/cli/cli/command/swarm/join_token_test.go +++ b/components/cli/cli/command/swarm/join_token_test.go @@ -1,19 +1,18 @@ package swarm import ( - "bytes" "fmt" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -90,14 +89,13 @@ func TestSwarmJoinTokenErrors(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newJoinTokenCommand( - test.NewFakeCliWithOutput(&fakeClient{ - swarmInspectFunc: tc.swarmInspectFunc, - swarmUpdateFunc: tc.swarmUpdateFunc, - infoFunc: tc.infoFunc, - nodeInspectFunc: tc.nodeInspectFunc, - }, buf)) + cli := test.NewFakeCli(&fakeClient{ + swarmInspectFunc: tc.swarmInspectFunc, + swarmUpdateFunc: tc.swarmUpdateFunc, + infoFunc: tc.infoFunc, + nodeInspectFunc: tc.nodeInspectFunc, + }) + cmd := newJoinTokenCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { cmd.Flags().Set(key, value) @@ -198,20 +196,17 @@ func TestSwarmJoinToken(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newJoinTokenCommand( - test.NewFakeCliWithOutput(&fakeClient{ - swarmInspectFunc: tc.swarmInspectFunc, - infoFunc: tc.infoFunc, - nodeInspectFunc: tc.nodeInspectFunc, - }, buf)) + cli := test.NewFakeCli(&fakeClient{ + swarmInspectFunc: tc.swarmInspectFunc, + infoFunc: tc.infoFunc, + nodeInspectFunc: tc.nodeInspectFunc, + }) + cmd := newJoinTokenCommand(cli) cmd.SetArgs(tc.args) for key, value := range tc.flags { cmd.Flags().Set(key, value) } assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("jointoken-%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("jointoken-%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/swarm/leave_test.go b/components/cli/cli/command/swarm/leave_test.go index b5b672fe47..cd8aeb8297 100644 --- a/components/cli/cli/command/swarm/leave_test.go +++ b/components/cli/cli/command/swarm/leave_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/docker/cli/cli/internal/test" - "github.com/docker/docker/pkg/testutil" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) diff --git a/components/cli/cli/command/swarm/testdata/jointoken-manager-rotate.golden b/components/cli/cli/command/swarm/testdata/jointoken-manager-rotate.golden index b4d0a48f66..4a978e7624 100644 --- a/components/cli/cli/command/swarm/testdata/jointoken-manager-rotate.golden +++ b/components/cli/cli/command/swarm/testdata/jointoken-manager-rotate.golden @@ -3,3 +3,4 @@ Successfully rotated manager join token. To add a manager to this swarm, run the following command: docker swarm join --token manager-join-token 127.0.0.1 + diff --git a/components/cli/cli/command/swarm/testdata/jointoken-manager.golden b/components/cli/cli/command/swarm/testdata/jointoken-manager.golden index 522b2968fe..7bcb73373c 100644 --- a/components/cli/cli/command/swarm/testdata/jointoken-manager.golden +++ b/components/cli/cli/command/swarm/testdata/jointoken-manager.golden @@ -1,3 +1,4 @@ To add a manager to this swarm, run the following command: docker swarm join --token manager-join-token 127.0.0.1 + diff --git a/components/cli/cli/command/swarm/testdata/jointoken-worker.golden b/components/cli/cli/command/swarm/testdata/jointoken-worker.golden index 899df703fd..e6c3ab9af8 100644 --- a/components/cli/cli/command/swarm/testdata/jointoken-worker.golden +++ b/components/cli/cli/command/swarm/testdata/jointoken-worker.golden @@ -1,3 +1,4 @@ To add a worker to this swarm, run the following command: docker swarm join --token worker-join-token 127.0.0.1 + diff --git a/components/cli/cli/command/swarm/unlock_key_test.go b/components/cli/cli/command/swarm/unlock_key_test.go index bb3935ceda..97c04fc628 100644 --- a/components/cli/cli/command/swarm/unlock_key_test.go +++ b/components/cli/cli/command/swarm/unlock_key_test.go @@ -5,14 +5,14 @@ import ( "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -167,8 +167,6 @@ func TestSwarmUnlockKey(t *testing.T) { cmd.Flags().Set(key, value) } assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("unlockkeys-%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("unlockkeys-%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/swarm/unlock_test.go b/components/cli/cli/command/swarm/unlock_test.go index 6663df1dba..c7aee0a3c7 100644 --- a/components/cli/cli/command/swarm/unlock_test.go +++ b/components/cli/cli/command/swarm/unlock_test.go @@ -6,10 +6,10 @@ import ( "testing" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/pkg/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) diff --git a/components/cli/cli/command/swarm/update_test.go b/components/cli/cli/command/swarm/update_test.go index b3cb02f25f..e2a0033916 100644 --- a/components/cli/cli/command/swarm/update_test.go +++ b/components/cli/cli/command/swarm/update_test.go @@ -6,14 +6,14 @@ import ( "testing" "time" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -174,8 +174,6 @@ func TestSwarmUpdate(t *testing.T) { } cmd.SetOutput(cli.OutBuffer()) assert.NoError(t, cmd.Execute()) - actual := cli.OutBuffer().String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("update-%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("update-%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/system/info.go b/components/cli/cli/command/system/info.go index 73f1765435..e1b5b08c63 100644 --- a/components/cli/cli/command/system/info.go +++ b/components/cli/cli/command/system/info.go @@ -54,7 +54,7 @@ func runInfo(dockerCli *command.DockerCli, opts *infoOptions) error { } // nolint: gocyclo -func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error { +func prettyPrintInfo(dockerCli command.Cli, info types.Info) error { fmt.Fprintf(dockerCli.Out(), "Containers: %d\n", info.Containers) fmt.Fprintf(dockerCli.Out(), " Running: %d\n", info.ContainersRunning) fmt.Fprintf(dockerCli.Out(), " Paused: %d\n", info.ContainersPaused) @@ -76,7 +76,7 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error { fprintfIfNotEmpty(dockerCli.Out(), "Logging Driver: %s\n", info.LoggingDriver) fprintfIfNotEmpty(dockerCli.Out(), "Cgroup Driver: %s\n", info.CgroupDriver) - fmt.Fprintf(dockerCli.Out(), "Plugins: \n") + fmt.Fprintf(dockerCli.Out(), "Plugins:\n") fmt.Fprintf(dockerCli.Out(), " Volume:") fmt.Fprintf(dockerCli.Out(), " %s", strings.Join(info.Plugins.Volume, " ")) fmt.Fprintf(dockerCli.Out(), "\n") @@ -130,6 +130,7 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error { fmt.Fprintf(dockerCli.Out(), " %s: %s\n", entry.Protocol, entry.URL) } } + fmt.Fprintf(dockerCli.Out(), " Autolock Managers: %v\n", info.Swarm.Cluster.Spec.EncryptionConfig.AutoLockManagers) fmt.Fprintf(dockerCli.Out(), " Root Rotation In Progress: %v\n", info.Swarm.Cluster.RootRotationInProgress) } fmt.Fprintf(dockerCli.Out(), " Node Address: %s\n", info.Swarm.NodeAddr) @@ -325,7 +326,7 @@ func prettyPrintInfo(dockerCli *command.DockerCli, info types.Info) error { return nil } -func printStorageDriverWarnings(dockerCli *command.DockerCli, info types.Info) { +func printStorageDriverWarnings(dockerCli command.Cli, info types.Info) { if info.DriverStatus == nil { return } diff --git a/components/cli/cli/command/system/info_test.go b/components/cli/cli/command/system/info_test.go new file mode 100644 index 0000000000..4291623d1a --- /dev/null +++ b/components/cli/cli/command/system/info_test.go @@ -0,0 +1,237 @@ +package system + +import ( + "encoding/base64" + "net" + "testing" + "time" + + "github.com/docker/cli/internal/test" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/api/types/swarm" + "github.com/gotestyourself/gotestyourself/golden" + "github.com/stretchr/testify/assert" +) + +// helper function that base64 decodes a string and ignores the error +func base64Decode(val string) []byte { + decoded, _ := base64.StdEncoding.DecodeString(val) + return decoded +} + +var sampleInfoNoSwarm = types.Info{ + ID: "EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX", + Containers: 0, + ContainersRunning: 0, + ContainersPaused: 0, + ContainersStopped: 0, + Images: 0, + Driver: "aufs", + DriverStatus: [][2]string{ + {"Root Dir", "/var/lib/docker/aufs"}, + {"Backing Filesystem", "extfs"}, + {"Dirs", "0"}, + {"Dirperm1 Supported", "true"}, + }, + SystemStatus: nil, + Plugins: types.PluginsInfo{ + Volume: []string{"local"}, + Network: []string{"bridge", "host", "macvlan", "null", "overlay"}, + Authorization: nil, + Log: []string{"awslogs", "fluentd", "gcplogs", "gelf", "journald", "json-file", "logentries", "splunk", "syslog"}, + }, + MemoryLimit: true, + SwapLimit: true, + KernelMemory: true, + CPUCfsPeriod: true, + CPUCfsQuota: true, + CPUShares: true, + CPUSet: true, + IPv4Forwarding: true, + BridgeNfIptables: true, + BridgeNfIP6tables: true, + Debug: true, + NFd: 33, + OomKillDisable: true, + NGoroutines: 135, + SystemTime: "2017-08-24T17:44:34.077811894Z", + LoggingDriver: "json-file", + CgroupDriver: "cgroupfs", + NEventsListener: 0, + KernelVersion: "4.4.0-87-generic", + OperatingSystem: "Ubuntu 16.04.3 LTS", + OSType: "linux", + Architecture: "x86_64", + IndexServerAddress: "https://index.docker.io/v1/", + RegistryConfig: ®istry.ServiceConfig{ + AllowNondistributableArtifactsCIDRs: nil, + AllowNondistributableArtifactsHostnames: nil, + InsecureRegistryCIDRs: []*registry.NetIPNet{ + { + IP: net.ParseIP("127.0.0.0"), + Mask: net.IPv4Mask(255, 0, 0, 0), + }, + }, + IndexConfigs: map[string]*registry.IndexInfo{ + "docker.io": { + Name: "docker.io", + Mirrors: nil, + Secure: true, + Official: true, + }, + }, + Mirrors: nil, + }, + NCPU: 2, + MemTotal: 2097356800, + DockerRootDir: "/var/lib/docker", + HTTPProxy: "", + HTTPSProxy: "", + NoProxy: "", + Name: "system-sample", + Labels: []string{"provider=digitalocean"}, + ExperimentalBuild: false, + ServerVersion: "17.06.1-ce", + ClusterStore: "", + ClusterAdvertise: "", + Runtimes: map[string]types.Runtime{ + "runc": { + Path: "docker-runc", + Args: nil, + }, + }, + DefaultRuntime: "runc", + Swarm: swarm.Info{LocalNodeState: "inactive"}, + LiveRestoreEnabled: false, + Isolation: "", + InitBinary: "docker-init", + ContainerdCommit: types.Commit{ + ID: "6e23458c129b551d5c9871e5174f6b1b7f6d1170", + Expected: "6e23458c129b551d5c9871e5174f6b1b7f6d1170", + }, + RuncCommit: types.Commit{ + ID: "810190ceaa507aa2727d7ae6f4790c76ec150bd2", + Expected: "810190ceaa507aa2727d7ae6f4790c76ec150bd2", + }, + InitCommit: types.Commit{ + ID: "949e6fa", + Expected: "949e6fa", + }, + SecurityOptions: []string{"name=apparmor", "name=seccomp,profile=default"}, +} + +var sampleSwarmInfo = swarm.Info{ + NodeID: "qo2dfdig9mmxqkawulggepdih", + NodeAddr: "165.227.107.89", + LocalNodeState: "active", + ControlAvailable: true, + Error: "", + RemoteManagers: []swarm.Peer{ + { + NodeID: "qo2dfdig9mmxqkawulggepdih", + Addr: "165.227.107.89:2377", + }, + }, + Nodes: 1, + Managers: 1, + Cluster: &swarm.ClusterInfo{ + ID: "9vs5ygs0gguyyec4iqf2314c0", + Meta: swarm.Meta{ + Version: swarm.Version{Index: 11}, + CreatedAt: time.Date(2017, 8, 24, 17, 34, 19, 278062352, time.UTC), + UpdatedAt: time.Date(2017, 8, 24, 17, 34, 42, 398815481, time.UTC), + }, + Spec: swarm.Spec{ + Annotations: swarm.Annotations{ + Name: "default", + Labels: nil, + }, + Orchestration: swarm.OrchestrationConfig{ + TaskHistoryRetentionLimit: &[]int64{5}[0], + }, + Raft: swarm.RaftConfig{ + SnapshotInterval: 10000, + KeepOldSnapshots: &[]uint64{0}[0], + LogEntriesForSlowFollowers: 500, + ElectionTick: 3, + HeartbeatTick: 1, + }, + Dispatcher: swarm.DispatcherConfig{ + HeartbeatPeriod: 5000000000, + }, + CAConfig: swarm.CAConfig{ + NodeCertExpiry: 7776000000000000, + }, + TaskDefaults: swarm.TaskDefaults{}, + EncryptionConfig: swarm.EncryptionConfig{ + AutoLockManagers: true, + }, + }, + TLSInfo: swarm.TLSInfo{ + TrustRoot: ` +-----BEGIN CERTIFICATE----- +MIIBajCCARCgAwIBAgIUaFCW5xsq8eyiJ+Pmcv3MCflMLnMwCgYIKoZIzj0EAwIw +EzERMA8GA1UEAxMIc3dhcm0tY2EwHhcNMTcwODI0MTcyOTAwWhcNMzcwODE5MTcy +OTAwWjATMREwDwYDVQQDEwhzd2FybS1jYTBZMBMGByqGSM49AgEGCCqGSM49AwEH +A0IABDy7NebyUJyUjWJDBUdnZoV6GBxEGKO4TZPNDwnxDxJcUdLVaB7WGa4/DLrW +UfsVgh1JGik2VTiLuTMA1tLlNPOjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBQl16XFtaaXiUAwEuJptJlDjfKskDAKBggqhkjO +PQQDAgNIADBFAiEAo9fTQNM5DP9bHVcTJYfl2Cay1bFu1E+lnpmN+EYJfeACIGKH +1pCUkZ+D0IB6CiEZGWSHyLuXPM1rlP+I5KuS7sB8 +-----END CERTIFICATE----- +`, + CertIssuerSubject: base64Decode("MBMxETAPBgNVBAMTCHN3YXJtLWNh"), + CertIssuerPublicKey: base64Decode( + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEPLs15vJQnJSNYkMFR2dmhXoYHEQYo7hNk80PCfEPElxR0tVoHtYZrj8MutZR+xWCHUkaKTZVOIu5MwDW0uU08w=="), + }, + RootRotationInProgress: false, + }, +} + +func TestPrettyPrintInfo(t *testing.T) { + infoWithSwarm := sampleInfoNoSwarm + infoWithSwarm.Swarm = sampleSwarmInfo + + infoWithWarningsLinux := sampleInfoNoSwarm + infoWithWarningsLinux.MemoryLimit = false + infoWithWarningsLinux.SwapLimit = false + infoWithWarningsLinux.KernelMemory = false + infoWithWarningsLinux.OomKillDisable = false + infoWithWarningsLinux.CPUCfsQuota = false + infoWithWarningsLinux.CPUCfsPeriod = false + infoWithWarningsLinux.CPUShares = false + infoWithWarningsLinux.CPUSet = false + infoWithWarningsLinux.IPv4Forwarding = false + infoWithWarningsLinux.BridgeNfIptables = false + infoWithWarningsLinux.BridgeNfIP6tables = false + + for _, tc := range []struct { + dockerInfo types.Info + expectedGolden string + warningsGolden string + }{ + { + dockerInfo: sampleInfoNoSwarm, + expectedGolden: "docker-info-no-swarm", + }, + { + dockerInfo: infoWithSwarm, + expectedGolden: "docker-info-with-swarm", + }, + { + dockerInfo: infoWithWarningsLinux, + expectedGolden: "docker-info-no-swarm", + warningsGolden: "docker-info-warnings", + }, + } { + cli := test.NewFakeCli(&fakeClient{}) + assert.NoError(t, prettyPrintInfo(cli, tc.dockerInfo)) + golden.Assert(t, cli.OutBuffer().String(), tc.expectedGolden+".golden") + if tc.warningsGolden != "" { + golden.Assert(t, cli.ErrBuffer().String(), tc.warningsGolden+".golden") + } else { + assert.Equal(t, "", cli.ErrBuffer().String()) + } + } +} diff --git a/components/cli/cli/command/system/prune_test.go b/components/cli/cli/command/system/prune_test.go index 669166c998..0d694ce066 100644 --- a/components/cli/cli/command/system/prune_test.go +++ b/components/cli/cli/command/system/prune_test.go @@ -3,7 +3,7 @@ package system import ( "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/stretchr/testify/assert" ) diff --git a/components/cli/cli/command/system/testdata/docker-info-no-swarm.golden b/components/cli/cli/command/system/testdata/docker-info-no-swarm.golden new file mode 100644 index 0000000000..7a3e966735 --- /dev/null +++ b/components/cli/cli/command/system/testdata/docker-info-no-swarm.golden @@ -0,0 +1,51 @@ +Containers: 0 + Running: 0 + Paused: 0 + Stopped: 0 +Images: 0 +Server Version: 17.06.1-ce +Storage Driver: aufs + Root Dir: /var/lib/docker/aufs + Backing Filesystem: extfs + Dirs: 0 + Dirperm1 Supported: true +Logging Driver: json-file +Cgroup Driver: cgroupfs +Plugins: + Volume: local + Network: bridge host macvlan null overlay + Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog +Swarm: inactive +Runtimes: runc +Default Runtime: runc +Init Binary: docker-init +containerd version: 6e23458c129b551d5c9871e5174f6b1b7f6d1170 +runc version: 810190ceaa507aa2727d7ae6f4790c76ec150bd2 +init version: 949e6fa +Security Options: + apparmor + seccomp + Profile: default +Kernel Version: 4.4.0-87-generic +Operating System: Ubuntu 16.04.3 LTS +OSType: linux +Architecture: x86_64 +CPUs: 2 +Total Memory: 1.953GiB +Name: system-sample +ID: EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX +Docker Root Dir: /var/lib/docker +Debug Mode (client): false +Debug Mode (server): true + File Descriptors: 33 + Goroutines: 135 + System Time: 2017-08-24T17:44:34.077811894Z + EventsListeners: 0 +Registry: https://index.docker.io/v1/ +Labels: + provider=digitalocean +Experimental: false +Insecure Registries: + 127.0.0.0/8 +Live Restore Enabled: false + diff --git a/components/cli/cli/command/system/testdata/docker-info-warnings.golden b/components/cli/cli/command/system/testdata/docker-info-warnings.golden new file mode 100644 index 0000000000..a7a4d792b0 --- /dev/null +++ b/components/cli/cli/command/system/testdata/docker-info-warnings.golden @@ -0,0 +1,11 @@ +WARNING: No memory limit support +WARNING: No swap limit support +WARNING: No kernel memory limit support +WARNING: No oom kill disable support +WARNING: No cpu cfs quota support +WARNING: No cpu cfs period support +WARNING: No cpu shares support +WARNING: No cpuset support +WARNING: IPv4 forwarding is disabled +WARNING: bridge-nf-call-iptables is disabled +WARNING: bridge-nf-call-ip6tables is disabled diff --git a/components/cli/cli/command/system/testdata/docker-info-with-swarm.golden b/components/cli/cli/command/system/testdata/docker-info-with-swarm.golden new file mode 100644 index 0000000000..17bb70fa7f --- /dev/null +++ b/components/cli/cli/command/system/testdata/docker-info-with-swarm.golden @@ -0,0 +1,73 @@ +Containers: 0 + Running: 0 + Paused: 0 + Stopped: 0 +Images: 0 +Server Version: 17.06.1-ce +Storage Driver: aufs + Root Dir: /var/lib/docker/aufs + Backing Filesystem: extfs + Dirs: 0 + Dirperm1 Supported: true +Logging Driver: json-file +Cgroup Driver: cgroupfs +Plugins: + Volume: local + Network: bridge host macvlan null overlay + Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog +Swarm: active + NodeID: qo2dfdig9mmxqkawulggepdih + Is Manager: true + ClusterID: 9vs5ygs0gguyyec4iqf2314c0 + Managers: 1 + Nodes: 1 + Orchestration: + Task History Retention Limit: 5 + Raft: + Snapshot Interval: 10000 + Number of Old Snapshots to Retain: 0 + Heartbeat Tick: 1 + Election Tick: 3 + Dispatcher: + Heartbeat Period: 5 seconds + CA Configuration: + Expiry Duration: 3 months + Force Rotate: 0 + Autolock Managers: true + Root Rotation In Progress: false + Node Address: 165.227.107.89 + Manager Addresses: + 165.227.107.89:2377 +Runtimes: runc +Default Runtime: runc +Init Binary: docker-init +containerd version: 6e23458c129b551d5c9871e5174f6b1b7f6d1170 +runc version: 810190ceaa507aa2727d7ae6f4790c76ec150bd2 +init version: 949e6fa +Security Options: + apparmor + seccomp + Profile: default +Kernel Version: 4.4.0-87-generic +Operating System: Ubuntu 16.04.3 LTS +OSType: linux +Architecture: x86_64 +CPUs: 2 +Total Memory: 1.953GiB +Name: system-sample +ID: EKHL:QDUU:QZ7U:MKGD:VDXK:S27Q:GIPU:24B7:R7VT:DGN6:QCSF:2UBX +Docker Root Dir: /var/lib/docker +Debug Mode (client): false +Debug Mode (server): true + File Descriptors: 33 + Goroutines: 135 + System Time: 2017-08-24T17:44:34.077811894Z + EventsListeners: 0 +Registry: https://index.docker.io/v1/ +Labels: + provider=digitalocean +Experimental: false +Insecure Registries: + 127.0.0.0/8 +Live Restore Enabled: false + diff --git a/components/cli/cli/command/task/print_test.go b/components/cli/cli/command/task/print_test.go index 174ad72cd7..04ae1bb0ef 100644 --- a/components/cli/cli/command/task/print_test.go +++ b/components/cli/cli/command/task/print_test.go @@ -1,20 +1,18 @@ package task import ( - "bytes" "testing" "time" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/idresolver" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "golang.org/x/net/context" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" + . "github.com/docker/cli/internal/test/builders" "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/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -22,75 +20,60 @@ func TestTaskPrintWithQuietOption(t *testing.T) { quiet := true trunc := false noResolve := true - buf := new(bytes.Buffer) apiClient := &fakeClient{} - cli := test.NewFakeCliWithOutput(apiClient, buf) - tasks := []swarm.Task{ - *Task(TaskID("id-foo")), - } + cli := test.NewFakeCli(apiClient) + tasks := []swarm.Task{*Task(TaskID("id-foo"))} err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, formatter.TableFormatKey) assert.NoError(t, err) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "task-print-with-quiet-option.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "task-print-with-quiet-option.golden") } func TestTaskPrintWithNoTruncOption(t *testing.T) { quiet := false trunc := false noResolve := true - buf := new(bytes.Buffer) apiClient := &fakeClient{} - cli := test.NewFakeCliWithOutput(apiClient, buf) + cli := test.NewFakeCli(apiClient) tasks := []swarm.Task{ *Task(TaskID("id-foo-yov6omdek8fg3k5stosyp2m50")), } err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, "{{ .ID }}") assert.NoError(t, err) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "task-print-with-no-trunc-option.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "task-print-with-no-trunc-option.golden") } func TestTaskPrintWithGlobalService(t *testing.T) { quiet := false trunc := false noResolve := true - buf := new(bytes.Buffer) apiClient := &fakeClient{} - cli := test.NewFakeCliWithOutput(apiClient, buf) + cli := test.NewFakeCli(apiClient) tasks := []swarm.Task{ *Task(TaskServiceID("service-id-foo"), TaskNodeID("node-id-bar"), TaskSlot(0)), } err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, "{{ .Name }}") assert.NoError(t, err) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "task-print-with-global-service.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "task-print-with-global-service.golden") } func TestTaskPrintWithReplicatedService(t *testing.T) { quiet := false trunc := false noResolve := true - buf := new(bytes.Buffer) apiClient := &fakeClient{} - cli := test.NewFakeCliWithOutput(apiClient, buf) + cli := test.NewFakeCli(apiClient) tasks := []swarm.Task{ *Task(TaskServiceID("service-id-foo"), TaskSlot(1)), } err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, "{{ .Name }}") assert.NoError(t, err) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "task-print-with-replicated-service.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "task-print-with-replicated-service.golden") } func TestTaskPrintWithIndentation(t *testing.T) { quiet := false trunc := false noResolve := false - buf := new(bytes.Buffer) apiClient := &fakeClient{ serviceInspectWithRaw: func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { return *Service(ServiceName("service-name-foo")), nil, nil @@ -99,7 +82,7 @@ func TestTaskPrintWithIndentation(t *testing.T) { return *Node(NodeName("node-name-bar")), nil, nil }, } - cli := test.NewFakeCliWithOutput(apiClient, buf) + cli := test.NewFakeCli(apiClient) tasks := []swarm.Task{ *Task( TaskID("id-foo"), @@ -120,16 +103,13 @@ func TestTaskPrintWithIndentation(t *testing.T) { } err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, formatter.TableFormatKey) assert.NoError(t, err) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "task-print-with-indentation.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "task-print-with-indentation.golden") } func TestTaskPrintWithResolution(t *testing.T) { quiet := false trunc := false noResolve := false - buf := new(bytes.Buffer) apiClient := &fakeClient{ serviceInspectWithRaw: func(ref string, options types.ServiceInspectOptions) (swarm.Service, []byte, error) { return *Service(ServiceName("service-name-foo")), nil, nil @@ -138,13 +118,11 @@ func TestTaskPrintWithResolution(t *testing.T) { return *Node(NodeName("node-name-bar")), nil, nil }, } - cli := test.NewFakeCliWithOutput(apiClient, buf) + cli := test.NewFakeCli(apiClient) tasks := []swarm.Task{ *Task(TaskServiceID("service-id-foo"), TaskSlot(1)), } err := Print(context.Background(), cli, tasks, idresolver.New(apiClient, noResolve), trunc, quiet, "{{ .Name }} {{ .Node }}") assert.NoError(t, err) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "task-print-with-resolution.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "task-print-with-resolution.golden") } diff --git a/components/cli/cli/command/task/testdata/task-print-with-indentation.golden b/components/cli/cli/command/task/testdata/task-print-with-indentation.golden index 932126ad94..8fa174a442 100644 --- a/components/cli/cli/command/task/testdata/task-print-with-indentation.golden +++ b/components/cli/cli/command/task/testdata/task-print-with-indentation.golden @@ -1,3 +1,3 @@ -ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS -id-foo service-name-foo.1 myimage:mytag node-name-bar Ready Failed 2 hours ago -id-bar \_ service-name-foo.1 myimage:mytag node-name-bar Ready Failed 2 hours ago +ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS +id-foo service-name-foo.1 myimage:mytag node-name-bar Ready Failed 2 hours ago +id-bar \_ service-name-foo.1 myimage:mytag node-name-bar Ready Failed 2 hours ago diff --git a/components/cli/cli/command/volume/create_test.go b/components/cli/cli/command/volume/create_test.go index e458a26c46..d7c6cb5857 100644 --- a/components/cli/cli/command/volume/create_test.go +++ b/components/cli/cli/command/volume/create_test.go @@ -1,16 +1,15 @@ package volume import ( - "bytes" "io/ioutil" "reflect" "strings" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" volumetypes "github.com/docker/docker/api/types/volume" - "github.com/docker/docker/pkg/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -57,8 +56,7 @@ func TestVolumeCreateErrors(t *testing.T) { func TestVolumeCreateWithName(t *testing.T) { name := "foo" - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) { if body.Name != name { return types.Volume{}, errors.Errorf("expected name %q, got %q", name, body.Name) @@ -67,7 +65,9 @@ func TestVolumeCreateWithName(t *testing.T) { Name: body.Name, }, nil }, - }, buf) + }) + + buf := cli.OutBuffer() // Test by flags cmd := newCreateCommand(cli) @@ -95,8 +95,7 @@ func TestVolumeCreateWithFlags(t *testing.T) { } name := "banana" - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ volumeCreateFunc: func(body volumetypes.VolumesCreateBody) (types.Volume, error) { if body.Name != "" { return types.Volume{}, errors.Errorf("expected empty name, got %q", body.Name) @@ -114,7 +113,7 @@ func TestVolumeCreateWithFlags(t *testing.T) { Name: name, }, nil }, - }, buf) + }) cmd := newCreateCommand(cli) cmd.Flags().Set("driver", "foo") @@ -123,5 +122,5 @@ func TestVolumeCreateWithFlags(t *testing.T) { cmd.Flags().Set("label", "lbl1=v1") cmd.Flags().Set("label", "lbl2=v2") assert.NoError(t, cmd.Execute()) - assert.Equal(t, name, strings.TrimSpace(buf.String())) + assert.Equal(t, name, strings.TrimSpace(cli.OutBuffer().String())) } diff --git a/components/cli/cli/command/volume/inspect_test.go b/components/cli/cli/command/volume/inspect_test.go index 8f452d7dee..934e9b27d8 100644 --- a/components/cli/cli/command/volume/inspect_test.go +++ b/components/cli/cli/command/volume/inspect_test.go @@ -1,18 +1,17 @@ package volume import ( - "bytes" "fmt" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -54,11 +53,10 @@ func TestVolumeInspectErrors(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) cmd := newInspectCommand( - test.NewFakeCliWithOutput(&fakeClient{ + test.NewFakeCli(&fakeClient{ volumeInspectFunc: tc.volumeInspectFunc, - }, buf), + }), ) cmd.SetArgs(tc.args) for key, value := range tc.flags { @@ -96,17 +94,13 @@ func TestVolumeInspectWithoutFormat(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newInspectCommand( - test.NewFakeCliWithOutput(&fakeClient{ - volumeInspectFunc: tc.volumeInspectFunc, - }, buf), - ) + cli := test.NewFakeCli(&fakeClient{ + volumeInspectFunc: tc.volumeInspectFunc, + }) + cmd := newInspectCommand(cli) cmd.SetArgs(tc.args) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-inspect-without-format.%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("volume-inspect-without-format.%s.golden", tc.name)) } } @@ -136,17 +130,13 @@ func TestVolumeInspectWithFormat(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := newInspectCommand( - test.NewFakeCliWithOutput(&fakeClient{ - volumeInspectFunc: tc.volumeInspectFunc, - }, buf), - ) + cli := test.NewFakeCli(&fakeClient{ + volumeInspectFunc: tc.volumeInspectFunc, + }) + cmd := newInspectCommand(cli) cmd.SetArgs(tc.args) cmd.Flags().Set("format", tc.format) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-inspect-with-format.%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("volume-inspect-with-format.%s.golden", tc.name)) } } diff --git a/components/cli/cli/command/volume/list_test.go b/components/cli/cli/command/volume/list_test.go index 92cd62b597..264e1010a1 100644 --- a/components/cli/cli/command/volume/list_test.go +++ b/components/cli/cli/command/volume/list_test.go @@ -1,20 +1,19 @@ package volume import ( - "bytes" "io/ioutil" "testing" "github.com/docker/cli/cli/config/configfile" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" volumetypes "github.com/docker/docker/api/types/volume" "github.com/pkg/errors" // Import builders to get the builder function as package function - . "github.com/docker/cli/cli/internal/test/builders" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + . "github.com/docker/cli/internal/test/builders" + "github.com/docker/cli/internal/test/testutil" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -37,11 +36,10 @@ func TestVolumeListErrors(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) cmd := newListCommand( - test.NewFakeCliWithOutput(&fakeClient{ + test.NewFakeCli(&fakeClient{ volumeListFunc: tc.volumeListFunc, - }, buf), + }), ) cmd.SetArgs(tc.args) for key, value := range tc.flags { @@ -53,8 +51,7 @@ func TestVolumeListErrors(t *testing.T) { } func TestVolumeListWithoutFormat(t *testing.T) { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) { return volumetypes.VolumesListOKBody{ Volumes: []*types.Volume{ @@ -66,17 +63,14 @@ func TestVolumeListWithoutFormat(t *testing.T) { }, }, nil }, - }, buf) + }) cmd := newListCommand(cli) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "volume-list-without-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "volume-list-without-format.golden") } func TestVolumeListWithConfigFormat(t *testing.T) { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) { return volumetypes.VolumesListOKBody{ Volumes: []*types.Volume{ @@ -88,20 +82,17 @@ func TestVolumeListWithConfigFormat(t *testing.T) { }, }, nil }, - }, buf) + }) cli.SetConfigFile(&configfile.ConfigFile{ VolumesFormat: "{{ .Name }} {{ .Driver }} {{ .Labels }}", }) cmd := newListCommand(cli) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "volume-list-with-config-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "volume-list-with-config-format.golden") } func TestVolumeListWithFormat(t *testing.T) { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ volumeListFunc: func(filter filters.Args) (volumetypes.VolumesListOKBody, error) { return volumetypes.VolumesListOKBody{ Volumes: []*types.Volume{ @@ -113,11 +104,9 @@ func TestVolumeListWithFormat(t *testing.T) { }, }, nil }, - }, buf) + }) cmd := newListCommand(cli) cmd.Flags().Set("format", "{{ .Name }} {{ .Driver }} {{ .Labels }}") assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "volume-list-with-format.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "volume-list-with-format.golden") } diff --git a/components/cli/cli/command/volume/prune_test.go b/components/cli/cli/command/volume/prune_test.go index cf26d283f4..d913f535db 100644 --- a/components/cli/cli/command/volume/prune_test.go +++ b/components/cli/cli/command/volume/prune_test.go @@ -1,7 +1,6 @@ package volume import ( - "bytes" "fmt" "io/ioutil" "runtime" @@ -9,11 +8,12 @@ import ( "testing" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/internal/test" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/filters" - "github.com/docker/docker/pkg/testutil" - "github.com/docker/docker/pkg/testutil/golden" + "github.com/gotestyourself/gotestyourself/golden" + "github.com/gotestyourself/gotestyourself/skip" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -41,9 +41,9 @@ func TestVolumePruneErrors(t *testing.T) { } for _, tc := range testCases { cmd := NewPruneCommand( - test.NewFakeCliWithOutput(&fakeClient{ + test.NewFakeCli(&fakeClient{ volumePruneFunc: tc.volumePruneFunc, - }, ioutil.Discard), + }), ) cmd.SetArgs(tc.args) for key, value := range tc.flags { @@ -68,60 +68,45 @@ func TestVolumePruneForce(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) - cmd := NewPruneCommand( - test.NewFakeCliWithOutput(&fakeClient{ - volumePruneFunc: tc.volumePruneFunc, - }, buf), - ) + cli := test.NewFakeCli(&fakeClient{ + volumePruneFunc: tc.volumePruneFunc, + }) + cmd := NewPruneCommand(cli) cmd.Flags().Set("force", "true") assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), fmt.Sprintf("volume-prune.%s.golden", tc.name)) - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("volume-prune.%s.golden", tc.name)) } } + func TestVolumePrunePromptYes(t *testing.T) { - if runtime.GOOS == "windows" { - // FIXME(vdemeester) make it work.. - t.Skip("skipping this test on Windows") - } + // FIXME(vdemeester) make it work.. + skip.IfCondition(t, runtime.GOOS == "windows", "TODO: fix test on windows") + for _, input := range []string{"y", "Y"} { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ volumePruneFunc: simplePruneFunc, - }, buf) + }) cli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input)))) - cmd := NewPruneCommand( - cli, - ) + cmd := NewPruneCommand(cli) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "volume-prune-yes.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "volume-prune-yes.golden") } } func TestVolumePrunePromptNo(t *testing.T) { - if runtime.GOOS == "windows" { - // FIXME(vdemeester) make it work.. - t.Skip("skipping this test on Windows") - } + // FIXME(vdemeester) make it work.. + skip.IfCondition(t, runtime.GOOS == "windows", "TODO: fix test on windows") + for _, input := range []string{"n", "N", "no", "anything", "really"} { - buf := new(bytes.Buffer) - cli := test.NewFakeCliWithOutput(&fakeClient{ + cli := test.NewFakeCli(&fakeClient{ volumePruneFunc: simplePruneFunc, - }, buf) + }) cli.SetIn(command.NewInStream(ioutil.NopCloser(strings.NewReader(input)))) - cmd := NewPruneCommand( - cli, - ) + cmd := NewPruneCommand(cli) assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "volume-prune-no.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + golden.Assert(t, cli.OutBuffer().String(), "volume-prune-no.golden") } } diff --git a/components/cli/cli/command/volume/remove_test.go b/components/cli/cli/command/volume/remove_test.go index 3d01905681..fdfb1d788f 100644 --- a/components/cli/cli/command/volume/remove_test.go +++ b/components/cli/cli/command/volume/remove_test.go @@ -1,12 +1,11 @@ package volume import ( - "bytes" "io/ioutil" "testing" - "github.com/docker/cli/cli/internal/test" - "github.com/docker/docker/pkg/testutil" + "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" ) @@ -29,11 +28,10 @@ func TestVolumeRemoveErrors(t *testing.T) { }, } for _, tc := range testCases { - buf := new(bytes.Buffer) cmd := newRemoveCommand( - test.NewFakeCliWithOutput(&fakeClient{ + test.NewFakeCli(&fakeClient{ volumeRemoveFunc: tc.volumeRemoveFunc, - }, buf)) + })) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) @@ -41,8 +39,7 @@ func TestVolumeRemoveErrors(t *testing.T) { } func TestNodeRemoveMultiple(t *testing.T) { - buf := new(bytes.Buffer) - cmd := newRemoveCommand(test.NewFakeCliWithOutput(&fakeClient{}, buf)) + cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{})) cmd.SetArgs([]string{"volume1", "volume2"}) assert.NoError(t, cmd.Execute()) } diff --git a/components/cli/cli/compose/convert/compose_test.go b/components/cli/cli/compose/convert/compose_test.go index 57849dd9bc..bde0942069 100644 --- a/components/cli/cli/compose/convert/compose_test.go +++ b/components/cli/cli/compose/convert/compose_test.go @@ -6,7 +6,7 @@ import ( composetypes "github.com/docker/cli/cli/compose/types" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/network" - "github.com/docker/docker/pkg/testutil/tempfile" + "github.com/gotestyourself/gotestyourself/fs" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -107,12 +107,12 @@ func TestSecrets(t *testing.T) { namespace := Namespace{name: "foo"} secretText := "this is the first secret" - secretFile := tempfile.NewTempFile(t, "convert-secrets", secretText) + secretFile := fs.NewFile(t, "convert-secrets", fs.WithContent(secretText)) defer secretFile.Remove() source := map[string]composetypes.SecretConfig{ "one": { - File: secretFile.Name(), + File: secretFile.Path(), Labels: map[string]string{"monster": "mash"}, }, "ext": { @@ -138,12 +138,12 @@ func TestConfigs(t *testing.T) { namespace := Namespace{name: "foo"} configText := "this is the first config" - configFile := tempfile.NewTempFile(t, "convert-configs", configText) + configFile := fs.NewFile(t, "convert-configs", fs.WithContent(configText)) defer configFile.Remove() source := map[string]composetypes.ConfigObjConfig{ "one": { - File: configFile.Name(), + File: configFile.Path(), Labels: map[string]string{"monster": "mash"}, }, "ext": { diff --git a/components/cli/cli/compose/convert/service.go b/components/cli/cli/compose/convert/service.go index d4f14a7b16..48df2d7e02 100644 --- a/components/cli/cli/compose/convert/service.go +++ b/components/cli/cli/compose/convert/service.go @@ -366,7 +366,6 @@ func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container return nil, nil } var ( - err error timeout, interval, startPeriod time.Duration retries int ) @@ -379,23 +378,14 @@ func convertHealthcheck(healthcheck *composetypes.HealthCheckConfig) (*container }, nil } - if healthcheck.Timeout != "" { - timeout, err = time.ParseDuration(healthcheck.Timeout) - if err != nil { - return nil, err - } + if healthcheck.Timeout != nil { + timeout = *healthcheck.Timeout } - if healthcheck.Interval != "" { - interval, err = time.ParseDuration(healthcheck.Interval) - if err != nil { - return nil, err - } + if healthcheck.Interval != nil { + interval = *healthcheck.Interval } - if healthcheck.StartPeriod != "" { - startPeriod, err = time.ParseDuration(healthcheck.StartPeriod) - if err != nil { - return nil, err - } + if healthcheck.StartPeriod != nil { + startPeriod = *healthcheck.StartPeriod } if healthcheck.Retries != nil { retries = int(*healthcheck.Retries) diff --git a/components/cli/cli/compose/convert/service_test.go b/components/cli/cli/compose/convert/service_test.go index 3a0bf0e75e..1945e22d09 100644 --- a/components/cli/cli/compose/convert/service_test.go +++ b/components/cli/cli/compose/convert/service_test.go @@ -109,16 +109,18 @@ func TestConvertResourcesOnlyMemory(t *testing.T) { func TestConvertHealthcheck(t *testing.T) { retries := uint64(10) + timeout := 30 * time.Second + interval := 2 * time.Millisecond source := &composetypes.HealthCheckConfig{ Test: []string{"EXEC", "touch", "/foo"}, - Timeout: "30s", - Interval: "2ms", + Timeout: &timeout, + Interval: &interval, Retries: &retries, } expected := &container.HealthConfig{ Test: source.Test, - Timeout: 30 * time.Second, - Interval: 2 * time.Millisecond, + Timeout: timeout, + Interval: interval, Retries: 10, } diff --git a/components/cli/cli/compose/loader/full-example.yml b/components/cli/cli/compose/loader/full-example.yml index d193dccd31..70486e1bcc 100644 --- a/components/cli/cli/compose/loader/full-example.yml +++ b/components/cli/cli/compose/loader/full-example.yml @@ -114,6 +114,7 @@ services: interval: 10s timeout: 1s retries: 5 + start_period: 15s # Any valid image reference - repo, tag, id, sha image: redis diff --git a/components/cli/cli/compose/loader/loader.go b/components/cli/cli/compose/loader/loader.go index 2fb2630087..3aada2e220 100644 --- a/components/cli/cli/compose/loader/loader.go +++ b/components/cli/cli/compose/loader/loader.go @@ -93,11 +93,7 @@ func Load(configDetails types.ConfigDetails) (*types.Config, error) { } cfg.Configs, err = LoadConfigObjs(config["configs"], configDetails.WorkingDir) - if err != nil { - return nil, err - } - - return &cfg, nil + return &cfg, err } func interpolateConfig(configDict map[string]interface{}, lookupEnv template.Mapping) (map[string]map[string]interface{}, error) { @@ -576,7 +572,6 @@ func transformServiceVolumeConfig(data interface{}) (interface{}, error) { default: return data, errors.Errorf("invalid type %T for service volume", value) } - } func transformServiceNetworkMap(value interface{}) (interface{}, error) { diff --git a/components/cli/cli/compose/loader/loader_test.go b/components/cli/cli/compose/loader/loader_test.go index 4ffc333f31..fb866cf143 100644 --- a/components/cli/cli/compose/loader/loader_test.go +++ b/components/cli/cli/compose/loader/loader_test.go @@ -584,6 +584,21 @@ services: assert.Contains(t, forbidden, "extends") } +func TestInvalidResource(t *testing.T) { + _, err := loadYAML(` + version: "3" + services: + foo: + image: busybox + deploy: + resources: + impossible: + x: 1 +`) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Additional property impossible is not allowed") +} + func TestInvalidExternalAndDriverCombination(t *testing.T) { _, err := loadYAML(` version: "3" @@ -741,10 +756,11 @@ func TestFullExample(t *testing.T) { "somehost": "162.242.195.82", }, HealthCheck: &types.HealthCheckConfig{ - Test: types.HealthCheckTest([]string{"CMD-SHELL", "echo \"hello world\""}), - Interval: "10s", - Timeout: "1s", - Retries: uint64Ptr(5), + Test: types.HealthCheckTest([]string{"CMD-SHELL", "echo \"hello world\""}), + Interval: durationPtr(10 * time.Second), + Timeout: durationPtr(1 * time.Second), + Retries: uint64Ptr(5), + StartPeriod: durationPtr(15 * time.Second), }, Hostname: "foo", Image: "redis", diff --git a/components/cli/cli/compose/loader/volume_test.go b/components/cli/cli/compose/loader/volume_test.go index f1b90fe8ea..2f2e50a905 100644 --- a/components/cli/cli/compose/loader/volume_test.go +++ b/components/cli/cli/compose/loader/volume_test.go @@ -5,7 +5,7 @@ import ( "testing" "github.com/docker/cli/cli/compose/types" - "github.com/docker/docker/pkg/testutil" + "github.com/docker/cli/internal/test/testutil" "github.com/stretchr/testify/assert" ) @@ -200,3 +200,13 @@ func TestParseVolumeSplitCases(t *testing.T) { assert.Equal(t, expected, parsed.Source != "", msg) } } + +func TestParseVolumeInvalidEmptySpec(t *testing.T) { + _, err := ParseVolume("") + testutil.ErrorContains(t, err, "invalid empty volume spec") +} + +func TestParseVolumeInvalidSections(t *testing.T) { + _, err := ParseVolume("/foo::rw") + testutil.ErrorContains(t, err, "invalid spec") +} diff --git a/components/cli/cli/compose/schema/bindata.go b/components/cli/cli/compose/schema/bindata.go index 4f34a8b6e9..f295006ed5 100644 --- a/components/cli/cli/compose/schema/bindata.go +++ b/components/cli/cli/compose/schema/bindata.go @@ -72,7 +72,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _dataConfig_schema_v30Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5a\x4b\x8f\xdb\x38\x12\xbe\xfb\x57\x08\x4a\x6e\x71\x77\x07\xd8\x60\x81\xcd\x6d\x8f\x7b\xda\x39\x4f\x43\x11\x68\xa9\x6c\x33\x4d\x91\x4c\x91\x72\xda\x09\xfc\xdf\x07\xd4\xcb\x14\x4d\x8a\xb2\xad\x3c\x30\x98\x53\xb7\xc5\xaa\x62\xbd\xf8\x55\xb1\xa4\xef\xab\x24\x49\xdf\xaa\x62\x0f\x15\x49\x3f\x26\xe9\x5e\x6b\xf9\xf1\xe9\xe9\xb3\x12\xfc\xa1\x7d\xfa\x28\x70\xf7\x54\x22\xd9\xea\x87\xf7\x1f\x9e\xda\x67\x6f\xd2\xb5\xe1\xa3\xa5\x61\x29\x04\xdf\xd2\x5d\xde\xae\xe4\x87\x7f\x3d\xbe\x7f\x34\xec\x2d\x89\x3e\x4a\x30\x44\x62\xf3\x19\x0a\xdd\x3e\x43\xf8\x52\x53\x04\xc3\xfc\x9c\x1e\x00\x15\x15\x3c\xcd\xd6\x2b\xb3\x26\x51\x48\x40\x4d\x41\xa5\x1f\x13\xa3\x5c\x92\x0c\x24\xfd\x03\x4b\xac\xd2\x48\xf9\x2e\x6d\x1e\x9f\x1a\x09\x49\x92\x2a\xc0\x03\x2d\x2c\x09\x83\xaa\x6f\x9e\xce\xf2\x9f\x06\xb2\xb5\x2b\xd5\x52\xb6\x79\x2e\x89\xd6\x80\xfc\x8f\x4b\xdd\x9a\xe5\x4f\xcf\xe4\xe1\xdb\x7f\x1f\xfe\x7c\xff\xf0\x9f\xc7\xfc\x21\x7b\xf7\x76\xb4\x6c\xfc\x8b\xb0\x6d\xb7\x2f\x61\x4b\x39\xd5\x54\xf0\x61\xff\x74\xa0\x3c\x75\xff\x9d\x86\x8d\x49\x59\x36\xc4\x84\x8d\xf6\xde\x12\xa6\x60\x6c\x33\x07\xfd\x55\xe0\x4b\xcc\xe6\x81\xec\x17\xd9\xdc\xed\xef\xb1\x79\x6c\xce\x41\xb0\xba\x8a\x46\xb0\xa7\xfa\x45\xc6\xb4\xdb\xdf\x17\xbf\x55\x6f\xf4\x24\x6d\x4b\x61\xed\xdd\x28\x38\xca\x76\x9f\xab\x7c\xd9\x16\xf6\xd5\xe0\xac\x80\x97\x4a\x90\x4c\x1c\xcd\xb3\x80\x3f\x5a\x82\x0a\xb8\x4e\x07\x17\x24\x49\xba\xa9\x29\x2b\x5d\x8f\x0a\x0e\xff\x37\x22\x9e\xad\x87\x49\xf2\xdd\x3d\xd8\x96\x9c\x66\x7d\xf4\x2b\x1c\xf0\x61\x3d\x60\xcb\xb0\x5e\x08\xae\xe1\x55\x37\x46\x4d\x6f\xdd\xba\x40\x14\x2f\x80\x5b\xca\x60\x2e\x07\xc1\x9d\x9a\x70\x19\xa3\x4a\xe7\x02\xf3\x92\x16\x3a\x3d\x39\xec\x17\xf2\xe2\xf9\x34\xb0\x5a\xbf\xb2\x95\x47\x60\x5a\x10\x99\x93\xb2\x1c\xd9\x41\x10\xc9\x31\x5d\x27\x29\xd5\x50\x29\xbf\x89\x49\x5a\x73\xfa\xa5\x86\xff\x75\x24\x1a\x6b\x70\xe5\x96\x28\xe4\xf2\x82\x77\x28\x6a\x99\x4b\x82\x26\xc1\xa6\xdd\x9f\x16\xa2\xaa\x08\x5f\x2a\xeb\xae\xb1\x63\x86\xe7\x05\xd7\x84\x72\xc0\x9c\x93\x2a\x96\x48\xe6\xd4\x01\x2f\x55\xde\xd6\xbf\xc9\x34\xda\xe6\x2d\xbf\x72\x04\x0c\xc5\x70\xd1\x78\x94\x7c\x2a\xb1\x5b\x31\x26\xb5\x8d\x6e\xa9\xc3\x98\x2b\x20\x58\xec\x6f\xe4\x17\x15\xa1\x7c\x8e\xef\x80\x6b\x3c\x4a\x41\xdb\x7c\xf9\xed\x12\x01\xf8\x21\x1f\xb0\xe4\x6a\x37\x00\x3f\x50\x14\xbc\xea\x4f\xc3\x1c\x80\x19\x40\xde\xf0\xbf\x4a\xa1\xc0\x75\x8c\x63\xa0\xbd\x34\x98\x3a\xf2\x49\xcf\xf1\xdc\x1b\xbe\x4e\x52\x5e\x57\x1b\x40\xd3\xd2\x8d\x28\xb7\x02\x2b\x62\x94\xed\xf7\xb6\x96\x47\x9e\xf6\x64\x9e\xed\x40\xdb\x06\x53\xd6\x09\xcb\x19\xe5\x2f\xcb\xa7\x38\xbc\x6a\x24\xf9\x5e\x28\x3d\x1f\xc3\x2d\xf6\x3d\x10\xa6\xf7\xc5\x1e\x8a\x97\x09\x76\x9b\x6a\xc4\x2d\x94\x9e\x93\xe4\xb4\x22\xbb\x38\x91\x2c\x62\x24\x8c\x6c\x80\xdd\x64\xe7\xa2\xce\xb7\xc4\x8a\xdd\xce\x90\x86\x32\xee\xa2\x73\xe9\x96\x63\x35\xbf\x44\x7a\x00\x9c\x5b\xc0\x85\x3c\x37\x5c\xee\x62\xbc\x01\x49\xe2\xdd\xe7\x88\xf4\xd3\x63\xdb\x7c\x4e\x9c\xaa\xe6\x3f\xc6\xd2\xcc\x6d\x17\x12\xa7\xee\xfb\x9e\x38\x16\xce\x6b\x28\x46\x51\xa9\x48\x61\xfa\x06\x04\x15\x88\xeb\x99\xb4\x6b\xf6\xf3\x4a\x94\xa1\x04\xbd\x20\x76\x7d\x13\x44\xea\xab\x0b\x61\x72\x53\xff\x38\x2b\x74\xd1\x0b\x44\xc4\x9a\x90\x7a\x73\xd5\x3c\xab\x1b\x4f\xb1\x86\x8e\x30\x4a\x14\xc4\x0f\x7b\xd0\x91\x23\x69\x54\x1e\x3e\xcc\xcc\x09\x1f\xef\xbf\x27\x79\x03\xac\x41\x99\xf3\x7b\xe4\x88\xa8\xb3\x2a\xcd\x71\xf3\x29\x92\x45\x4e\xdb\x0f\x6e\xe1\x25\x2d\xc3\x58\xd1\x20\x84\x7d\xc0\xa4\x40\x7d\x71\xba\x7e\x4e\xb9\x6f\xb7\xbe\xbb\xda\x4b\xa4\x07\xca\x60\x07\xe3\x5b\xcb\x46\x08\x06\x84\x8f\xa0\x07\x81\x94\xb9\xe0\xec\x38\x83\x52\x69\x82\xd1\x0b\x85\x82\xa2\x46\xaa\x8f\xb9\x90\x7a\xf1\x3e\x43\xed\xab\x5c\xd1\x6f\x30\x8e\xe6\x19\xef\x3b\x41\xd9\x88\xe7\xa8\x0a\x7d\x5b\xbd\x56\xba\xa4\x3c\x17\x12\x78\xd4\x3b\x4a\x0b\x99\xef\x90\x14\x90\x4b\x40\x2a\x4a\x9f\x81\x6b\x3b\xd6\x65\x8d\xc4\xec\x7f\x29\x46\xd1\x1d\x27\x2c\xe6\x68\x5d\xc9\xed\x8d\x17\x0b\xad\xe3\xe1\xae\x19\xad\x68\xf8\x1c\x78\x00\x76\x46\x0d\x68\xf1\xdf\x0f\xfb\x13\x90\x7f\xd6\x94\x72\x0d\x3b\x40\x1f\x52\x4e\x74\x1d\xd3\x4d\xc7\x8c\x6e\x63\x4f\x70\x1c\xd0\x09\x3d\x1a\x06\x25\xb6\xda\xcf\xe0\xeb\x45\xbc\x7a\x8d\x86\xbf\x8d\xbc\x75\xa7\x48\xe6\xa5\xbf\x0a\xce\x5d\x35\xb2\x20\xa2\x9e\xbc\x88\x5a\xab\x68\x63\xd8\xd0\x70\x35\xd5\xd4\x0c\xa4\xd6\x14\x73\x51\xbc\x30\x8d\x92\x39\x04\x25\xf5\x6b\xbb\x72\x2c\xbb\x62\x8e\xec\xdc\x59\x7a\x01\xbe\x89\xa2\x4d\x1a\x9d\xc0\x4e\x4f\x37\x3b\xa2\xe0\xe4\x91\x2a\xb2\x71\x66\x6e\xbe\xc3\x6d\xb2\x11\x0f\x71\x8c\x41\xd0\x48\x9d\xb8\x74\x68\x3b\xc2\x13\x50\xbf\xe7\xe0\x40\xd3\x0a\x44\xed\xaf\x59\x2b\x3b\xbf\x3b\xa6\xd4\x9a\xcc\x46\x82\x6a\x51\xba\x31\x7d\x1e\x82\xda\xf7\x17\xd1\xc0\xcd\x39\x24\x08\x92\xd1\x82\xa8\x18\x10\xdd\x71\x41\xad\x65\x49\x34\xe4\xed\x8b\xaa\xab\xa0\x7f\x02\xf3\x25\x41\xc2\x18\x30\xaa\xaa\x39\x18\x9a\x96\xc0\xc8\xf1\xa6\xf2\xd9\xb0\x6f\x09\x65\x35\x42\x4e\x0a\xdd\xbd\x0b\x8b\xe4\x5c\x5a\x09\x4e\xb5\xf0\x22\xc4\xbc\x2d\x2b\xf2\x9a\xf7\xdb\x36\x24\xde\x03\x13\x6c\xeb\xe6\xde\x2d\xad\x4c\x50\xa2\xc6\xe2\xc2\xd9\x37\x87\xe8\x5c\xeb\x03\x19\xd3\xef\x78\x61\x3a\x82\x32\x48\x32\x5c\xfd\xa3\xfc\xd1\xd2\xd2\xf5\x99\xb9\x14\x8c\x16\xc7\xa5\x2c\x2c\x04\x6f\x9d\x3c\x27\x21\xee\xcc\x40\x93\x0e\xa6\x15\xaa\xa4\x8e\x1e\xd6\x86\xe1\x2b\xe5\xa5\xf8\x7a\xc5\x86\xcb\xa5\x92\x64\xa4\x00\x07\xef\xee\x75\xb4\xd2\x48\x28\xd7\x57\x97\xf3\x7b\xcd\xba\xa3\x9a\x0f\xf9\x19\x41\xfd\x81\x2e\xfe\x26\x35\x80\xf4\x85\xac\xa3\xf3\xa0\x0a\x2a\x81\xde\x04\x5c\xe0\xcd\x77\xcc\xc4\x9e\x6c\x81\xaa\x36\x6b\x80\xd8\x51\x99\xfb\xe2\xe2\xb7\x8d\xf8\x90\x30\x8b\x03\x12\x95\xa4\x5a\xea\x74\xcc\x1e\xa9\xa6\xde\x1a\x9c\x4c\x8f\x22\x92\xf0\x38\x22\xa6\x75\x5c\xf7\x8e\x42\xd5\x1b\x0e\x93\x1d\x95\xe5\x4f\xdf\x7b\xde\xf9\xd7\x94\x53\xf8\x52\x72\x1f\xe8\xf5\x6f\x43\x02\x51\x7d\x1e\x7a\xe6\xf5\xe0\xab\x6c\x76\x88\x83\xaf\x22\x96\xd3\xbf\x69\xdf\xdd\x11\x81\xaf\xcf\xbf\xb2\x13\xbc\x03\x5c\xba\x4f\x3c\x22\xd8\xd2\x51\xfd\x03\x2d\x7f\x93\x44\xfc\x79\xf9\xe5\x4c\xb3\xac\x3c\xbb\xbc\x68\x4e\xa5\xc4\xec\x31\x7e\xc7\x91\x8d\xd5\x70\xc9\x3c\x5f\xda\x8d\x61\x79\x6a\x7a\xd1\x93\x04\xc6\xba\xce\xa6\x9d\x13\xa7\x2d\x5f\x30\xc3\x1f\xdf\x4d\x14\x9f\xa9\xd7\x6d\x3f\x08\xb5\x17\x98\x0c\xf9\x63\xea\x74\xac\xbd\x77\x2f\x3f\x17\x0b\x80\x9a\xc5\x7f\xf1\xf1\x98\xb1\x93\x1f\x2f\x06\x21\xdf\xc7\xd3\xbd\xf6\xc3\xaf\x6c\xe4\x1f\x87\xa4\x7d\x79\x6d\x41\x4a\x66\x37\xf1\xa1\x30\x7a\x3f\x29\x73\x67\x8b\xfd\xa7\x5d\x99\x1f\xae\x56\xf6\xdf\xe6\x33\xbc\xd5\x69\xf5\x57\x00\x00\x00\xff\xff\x78\x30\xec\x51\x0e\x2b\x00\x00") +var _dataConfig_schema_v30Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5a\x4b\x8f\xdb\x38\x12\xbe\xfb\x57\x08\x4a\x6e\x71\x77\x07\xd8\x60\x81\xcd\x6d\x8f\x7b\xda\x39\x4f\x43\x11\x68\xa9\x6c\x33\x4d\x91\x4c\x91\x72\xda\x09\xfc\xdf\x07\xd4\xcb\x14\x4d\x8a\xb2\xad\x3c\x30\x98\x53\xb7\xc5\xaa\x62\xbd\xf8\x55\xb1\xa4\xef\xab\x24\x49\xdf\xaa\x62\x0f\x15\x49\x3f\x26\xe9\x5e\x6b\xf9\xf1\xe9\xe9\xb3\x12\xfc\xa1\x7d\xfa\x28\x70\xf7\x54\x22\xd9\xea\x87\xf7\x1f\x9e\xda\x67\x6f\xd2\xb5\xe1\xa3\xa5\x61\x29\x04\xdf\xd2\x5d\xde\xae\xe4\x87\x7f\x3d\xbe\x7f\x34\xec\x2d\x89\x3e\x4a\x30\x44\x62\xf3\x19\x0a\xdd\x3e\x43\xf8\x52\x53\x04\xc3\xfc\x9c\x1e\x00\x15\x15\x3c\xcd\xd6\x2b\xb3\x26\x51\x48\x40\x4d\x41\xa5\x1f\x13\xa3\x5c\x92\x0c\x24\xfd\x03\x4b\xac\xd2\x48\xf9\x2e\x6d\x1e\x9f\x1a\x09\x49\x92\x2a\xc0\x03\x2d\x2c\x09\x83\xaa\x6f\x9e\xce\xf2\x9f\x06\xb2\xb5\x2b\xd5\x52\xb6\x79\x2e\x89\xd6\x80\xfc\x8f\x4b\xdd\x9a\xe5\x4f\xcf\xe4\xe1\xdb\x7f\x1f\xfe\x7c\xff\xf0\x9f\xc7\xfc\x21\x7b\xf7\x76\xb4\x6c\xfc\x8b\xb0\x6d\xb7\x2f\x61\x4b\x39\xd5\x54\xf0\x61\xff\x74\xa0\x3c\x75\xff\x9d\x86\x8d\x49\x59\x36\xc4\x84\x8d\xf6\xde\x12\xa6\x60\x6c\x33\x07\xfd\x55\xe0\x4b\xcc\xe6\x81\xec\x17\xd9\xdc\xed\xef\xb1\x79\x6c\xce\x41\xb0\xba\x8a\x46\xb0\xa7\xfa\x45\xc6\xb4\xdb\xdf\x17\xbf\x55\x6f\xf4\x24\x6d\x4b\x61\xed\xdd\x28\x38\xca\x76\x9f\xab\x7c\xd9\x16\xf6\xd5\xe0\xac\x80\x97\x4a\x90\x4c\x1c\xcd\xb3\x80\x3f\x5a\x82\x0a\xb8\x4e\x07\x17\x24\x49\xba\xa9\x29\x2b\x5d\x8f\x0a\x0e\xff\x37\x22\x9e\xad\x87\x49\xf2\xdd\x3d\xd8\x96\x9c\x66\x7d\xf4\x2b\x1c\xf0\x61\x3d\x60\xcb\xb0\x5e\x08\xae\xe1\x55\x37\x46\x4d\x6f\xdd\xba\x40\x14\x2f\x80\x5b\xca\x60\x2e\x07\xc1\x9d\x9a\x70\x19\xa3\x4a\xe7\x02\xf3\x92\x16\x3a\x3d\x39\xec\x17\xf2\xe2\xf9\x34\xb0\x5a\xbf\xb2\x95\x47\x60\x5a\x10\x99\x93\xb2\x1c\xd9\x41\x10\xc9\x31\x5d\x27\x29\xd5\x50\x29\xbf\x89\x49\x5a\x73\xfa\xa5\x86\xff\x75\x24\x1a\x6b\x70\xe5\x96\x28\xe4\xf2\x82\x77\x28\x6a\x99\x4b\x82\x26\xc1\xa6\xdd\x9f\x16\xa2\xaa\x08\x5f\x2a\xeb\xae\xb1\x63\x86\xe7\x05\xd7\x84\x72\xc0\x9c\x93\x2a\x96\x48\xe6\xd4\x01\x2f\x55\xde\xd6\xbf\xc9\x34\xda\xe6\x2d\xbf\x72\x04\x0c\xc5\x70\xd1\x78\x94\x7c\x2a\xb1\x5b\x31\x26\xb5\x8d\x6e\xa9\xc3\x98\x2b\x20\x58\xec\x6f\xe4\x17\x15\xa1\x7c\x8e\xef\x80\x6b\x3c\x4a\x41\xdb\x7c\xf9\xed\x12\x01\xf8\x21\x1f\xb0\xe4\x6a\x37\x00\x3f\x50\x14\xbc\xea\x4f\xc3\x1c\x80\x19\x40\xde\xf0\xbf\x4a\xa1\xc0\x75\x8c\x63\xa0\xbd\x34\x98\x3a\xf2\x49\xcf\xf1\xdc\x1b\xbe\x4e\x52\x5e\x57\x1b\x40\xd3\xd2\x8d\x28\xb7\x02\x2b\x62\x94\xed\xf7\xb6\x96\x47\x9e\xf6\x64\x9e\xed\x40\xdb\x06\x53\xd6\x09\xcb\x19\xe5\x2f\xcb\xa7\x38\xbc\x6a\x24\xf9\x5e\x28\x3d\x1f\xc3\x2d\xf6\x3d\x10\xa6\xf7\xc5\x1e\x8a\x97\x09\x76\x9b\x6a\xc4\x2d\x94\x9e\x93\xe4\xb4\x22\xbb\x38\x91\x2c\x62\x24\x8c\x6c\x80\xdd\x64\xe7\xa2\xce\xb7\xc4\x8a\xdd\xce\x90\x86\x32\xee\xa2\x73\xe9\x96\x63\x35\xbf\x44\x7a\x00\x9c\x5b\xc0\x85\x3c\x37\x5c\xee\x62\xbc\x01\x49\xe2\xdd\xe7\x88\xf4\xd3\x63\xdb\x7c\x4e\x9c\xaa\xe6\x3f\xc6\xd2\xcc\x6d\x17\x12\xa7\xee\xfb\x9e\x38\x16\xce\x6b\x28\x46\x51\xa9\x48\x61\xfa\x06\x04\x15\x88\xeb\x99\xb4\x6b\xf6\xf3\x4a\x94\xa1\x04\xbd\x20\x76\x7d\x13\x44\xea\xab\x0b\x61\x72\x53\xff\x38\x2b\x74\xd1\x0b\x44\xc4\x9a\x90\x7a\x73\xd5\x3c\xab\x1b\x4f\xb1\x86\x8e\x30\x4a\x14\xc4\x0f\x7b\xd0\x91\x23\x69\x54\x1e\x3e\xcc\xcc\x09\x1f\xef\xbf\x27\x79\x03\xac\x41\x99\xf3\x7b\xe4\x88\xa8\xb3\x2a\xcd\x71\xf3\x29\x92\x45\x4e\xdb\x0f\x6e\xe1\x25\x2d\xc3\x58\xd1\x20\x84\x7d\xc0\xa4\x40\x7d\x71\xba\x7e\x4e\xb9\x6f\xb7\xbe\xbb\xda\x4b\xa4\x07\xca\x60\x07\xe3\x5b\xcb\x46\x08\x06\x84\x8f\xa0\x07\x81\x94\xb9\xe0\xec\x38\x83\x52\x69\x82\xd1\x0b\x85\x82\xa2\x46\xaa\x8f\xb9\x90\x7a\xf1\x3e\x43\xed\xab\x5c\xd1\x6f\x30\x8e\xe6\x19\xef\x3b\x41\xd9\x88\xe7\xa8\x0a\x7d\x5b\xbd\x56\xba\xa4\x3c\x17\x12\x78\xd4\x3b\x4a\x0b\x99\xef\x90\x14\x90\x4b\x40\x2a\x4a\x9f\x81\x6b\x3b\xd6\x65\x8d\xc4\xec\x7f\x29\x46\xd1\x1d\x27\x2c\xe6\x68\x5d\xc9\xed\x8d\x17\x0b\xad\xe3\xe1\xae\x19\xad\x68\xf8\x1c\x78\x00\x76\x46\x0d\x68\xf1\xdf\x0f\xfb\x13\x90\x7f\xd6\x94\x72\x0d\x3b\x40\x1f\x52\x4e\x74\x1d\xd3\x4d\xc7\x8c\x6e\x63\x4f\x70\x1c\xd0\x09\x3d\x1a\x06\x25\xb6\xda\xcf\xe0\xeb\x45\xbc\x7a\x8d\x86\xbf\x8d\xbc\x75\xa7\x48\xe6\xa5\xbf\x0a\xce\x5d\x35\xb2\x20\xa2\x9e\xbc\x88\x5a\xab\x68\x63\xd8\xd0\x70\x35\xd5\xd4\x0c\xa4\xd6\x14\x73\x51\xbc\x30\x8d\x92\x39\x04\x25\xf5\x6b\xbb\x72\x2c\xbb\x62\x8e\xec\xdc\x59\x7a\x01\xbe\x89\xa2\x4d\x1a\x9d\xc0\x4e\x4f\x37\x3b\xa2\xe0\xe4\x91\x2a\xb2\x71\x66\x6e\xbe\xc3\x6d\xb2\x11\x0f\x71\x8c\x41\xd0\x48\x9d\xb8\x74\x68\x3b\xc2\x13\x50\xbf\xe7\xe0\x40\xd3\x0a\x44\xed\xaf\x59\x2b\x3b\xbf\x3b\xa6\xd4\x9a\xcc\x46\x82\x6a\x51\xba\x31\x7d\x1e\x82\xda\xf7\x17\xd1\xc0\xcd\x39\x24\x08\x92\xd1\x82\xa8\x18\x10\xdd\x71\x41\xad\x65\x49\x34\xe4\xed\x8b\xaa\xab\xa0\x7f\x02\xf3\x25\x41\xc2\x18\x30\xaa\xaa\x39\x18\x9a\x96\xc0\xc8\xf1\xa6\xf2\xd9\xb0\x6f\x09\x65\x35\x42\x4e\x0a\xdd\xbd\x0b\x8b\xe4\x5c\x5a\x09\x4e\xb5\xf0\x22\xc4\xbc\x2d\x2b\xf2\x9a\xf7\xdb\x36\x24\xde\x03\x13\x6c\xeb\xe6\xde\x2d\xad\x4c\x50\xa2\xc6\xe2\xc2\xd9\x37\x87\xe8\x5c\xeb\x03\x19\xd3\xef\x78\x61\x3a\x82\x32\x48\x32\x5c\xfd\xa3\xfc\x8b\x7a\xc1\x34\xa4\xb9\x14\x8c\x16\xc7\xa5\x5c\x51\x08\xde\xea\x31\x27\x73\xee\x4c\x55\x93\x37\xa6\x67\xaa\xa4\x8e\x9e\xea\x86\xe1\x2b\xe5\xa5\xf8\x7a\xc5\x86\xcb\x79\x5b\x32\x52\x80\x03\x8c\xf7\x3a\x5a\x69\x24\x94\xeb\xab\xeb\xfe\xbd\x66\xdd\x51\xf6\x87\x44\x8e\x94\x87\x81\x2e\xfe\xca\x35\x50\x12\x0a\x59\x47\x07\x47\x15\x54\x02\xbd\x09\xb8\xc0\x2b\xf2\x98\x89\x3d\xd9\x02\xe5\x6f\xd6\xa4\xb1\xa3\x32\x17\xcb\xc5\xaf\x25\xf1\x69\x62\x16\x6f\x8a\xa9\x24\xd5\x52\xa7\x63\xf6\xec\x35\xf5\x16\xeb\x64\x7a\x66\x91\x84\xe7\x16\x31\xad\xe3\xba\x77\x14\xaa\xde\x70\x98\x6c\xbd\x2c\x7f\xfa\x5e\x08\xcf\xbf\xcf\x9c\xc2\xb7\x97\xfb\x40\xaf\x7f\x6d\x12\x88\xea\xf3\xd0\x5c\xaf\x07\x5f\x65\xb3\x43\x1c\x7c\x67\xb1\x9c\xfe\x4d\x9f\xef\xce\x12\x7c\x17\x82\x2b\x5b\xc6\x3b\xc0\xa5\xfb\x16\x24\x82\x2d\x1d\xd5\x3f\xd0\xf2\x37\x49\xc4\x9f\x97\x5f\xce\xd8\xcb\xca\xb3\xcb\x1b\xe9\x54\x4a\xcc\x9e\xf7\x77\x1c\xd9\x58\x0d\x97\xcc\xf3\x49\xde\x18\x96\xa7\xc6\x1c\x3d\x49\x60\xfe\xeb\x6c\xda\x39\x71\xda\xf2\x05\x33\xfc\xf1\xdd\x44\xf1\x99\x7a\x2f\xf7\x83\x50\x7b\x81\x11\x92\x3f\xa6\x4e\xc7\xda\x7b\xf7\xf2\xbb\xb2\x00\xa8\x59\xfc\x17\x5f\x99\x19\x3b\xf9\xf1\x62\x62\xf2\x7d\x3c\x06\x6c\xbf\x10\xcb\x46\xfe\x71\x48\xda\xb7\xdc\x16\xa4\x64\x76\x13\x1f\x0a\xa3\xf7\xdb\x33\x77\x08\xd9\x7f\x03\x96\xf9\xe1\x6a\x65\xff\x6d\xbe\xd7\x5b\x9d\x56\x7f\x05\x00\x00\xff\xff\xef\x66\x29\x26\x37\x2b\x00\x00") func dataConfig_schema_v30JsonBytes() ([]byte, error) { return bindataRead( @@ -92,7 +92,7 @@ func dataConfig_schema_v30Json() (*asset, error) { return a, nil } -var _dataConfig_schema_v31Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x1a\xcb\x8e\xdb\x36\xf0\xee\xaf\x10\x94\xdc\xe2\xdd\x4d\xd1\xa0\x40\x73\xeb\xb1\xa7\xf6\xdc\x85\x23\xd0\xd2\x58\x66\x96\x22\x19\x92\x72\xd6\x09\xfc\xef\x05\xf5\x32\x45\x91\x22\x6d\x2b\xd9\x45\xd1\xd3\xae\xc5\x99\xe1\xbc\x67\x38\xe4\xf7\x55\x92\xa4\x6f\x65\xbe\x87\x0a\xa5\x1f\x93\x74\xaf\x14\xff\xf8\xf0\xf0\x59\x32\x7a\xd7\x7e\xbd\x67\xa2\x7c\x28\x04\xda\xa9\xbb\xf7\x1f\x1e\xda\x6f\x6f\xd2\xb5\xc6\xc3\x85\x46\xc9\x19\xdd\xe1\x32\x6b\x57\xb2\xc3\xaf\xf7\xbf\xdc\x6b\xf4\x16\x44\x1d\x39\x68\x20\xb6\xfd\x0c\xb9\x6a\xbf\x09\xf8\x52\x63\x01\x1a\xf9\x31\x3d\x80\x90\x98\xd1\x74\xb3\x5e\xe9\x35\x2e\x18\x07\xa1\x30\xc8\xf4\x63\xa2\x99\x4b\x92\x01\xa4\xff\x60\x90\x95\x4a\x60\x5a\xa6\xcd\xe7\x53\x43\x21\x49\x52\x09\xe2\x80\x73\x83\xc2\xc0\xea\x9b\x87\x33\xfd\x87\x01\x6c\x6d\x53\x35\x98\x6d\xbe\x73\xa4\x14\x08\xfa\xf7\x94\xb7\x66\xf9\xd3\x23\xba\xfb\xf6\xc7\xdd\x3f\xef\xef\x7e\xbf\xcf\xee\x36\xef\xde\x8e\x96\xb5\x7e\x05\xec\xda\xed\x0b\xd8\x61\x8a\x15\x66\x74\xd8\x3f\x1d\x20\x4f\xdd\x7f\xa7\x61\x63\x54\x14\x0d\x30\x22\xa3\xbd\x77\x88\x48\x18\xcb\x4c\x41\x7d\x65\xe2\x29\x24\xf3\x00\xf6\x42\x32\x77\xfb\x3b\x64\x1e\x8b\x73\x60\xa4\xae\x82\x16\xec\xa1\x5e\x48\x98\x76\xfb\x65\xec\x27\x21\x17\xa0\xc2\x2e\xdb\x42\xbd\x98\xc7\xea\xed\x6f\x13\x78\xd5\x0b\x3d\x0b\xdb\x42\x18\x7b\x37\x0c\x8e\xc2\xdb\xa5\x2a\x57\x78\xf9\x75\x35\x28\xcb\xa3\xa5\x02\x38\x61\x47\xfd\xcd\xa3\x8f\x16\xa0\x02\xaa\xd2\x41\x05\x49\x92\x6e\x6b\x4c\x0a\x5b\xa3\x8c\xc2\x5f\x9a\xc4\xa3\xf1\x31\x49\xbe\xdb\x99\xcc\xa0\xd3\xac\x8f\x7e\xf9\x0d\x3e\xac\x7b\x64\x19\xd6\x73\x46\x15\x3c\xab\x46\xa8\xf9\xad\x5b\x15\xb0\xfc\x09\xc4\x0e\x13\x88\xc5\x40\xa2\x94\x33\x2a\x23\x58\xaa\x8c\x89\xac\xc0\xb9\x4a\x4f\x16\xfa\x84\x5e\xd8\x9f\x06\x54\xe3\xd7\x66\xe5\x20\x98\xe6\x88\x67\xa8\x28\x46\x72\x20\x21\xd0\x31\x5d\x27\x29\x56\x50\x49\xb7\x88\x49\x5a\x53\xfc\xa5\x86\x3f\x3b\x10\x25\x6a\xb0\xe9\x16\x82\xf1\xe5\x09\x97\x82\xd5\x3c\xe3\x48\x68\x07\x9b\x57\x7f\x9a\xb3\xaa\x42\x74\x29\xaf\xbb\x44\x8e\x08\xcd\x33\xaa\x10\xa6\x20\x32\x8a\xaa\x90\x23\xe9\xa8\x03\x5a\xc8\xac\x2d\xf8\xb3\x6e\xb4\xcb\x5a\x7c\x69\x11\x18\xaa\xff\xa2\xf6\x28\xe8\x9c\x63\xb7\x64\xb4\x6b\x6b\xde\x52\x0b\x31\x93\x80\x44\xbe\xbf\x12\x9f\x55\x08\xd3\x18\xdd\x01\x55\xe2\xc8\x19\x6e\xfd\xe5\xd5\x39\x02\xd0\x43\x36\xe4\x92\x8b\xd5\x00\xf4\x80\x05\xa3\x55\x1f\x0d\x31\x09\x66\x48\xf2\x1a\xff\x99\x33\x09\xb6\x62\x2c\x01\xcd\xa5\x41\xd4\x91\x4e\x7a\x8c\xc7\x5e\xf0\x75\x92\xd2\xba\xda\x82\xd0\x3d\xec\x08\x72\xc7\x44\x85\x34\xb3\xfd\xde\xc6\xf2\x48\xd3\x0e\xcf\x33\x15\x68\xca\xa0\xcb\x3a\x22\x19\xc1\xf4\x69\x79\x17\x87\x67\x25\x50\xb6\x67\x52\xc5\xe7\x70\x03\x7d\x0f\x88\xa8\x7d\xbe\x87\xfc\x69\x06\xdd\x84\x1a\x61\x33\xa9\x62\x9c\x1c\x57\xa8\x0c\x03\xf1\x3c\x04\x42\xd0\x16\xc8\x55\x72\x2e\xaa\x7c\x83\x2c\x2b\x4b\x0d\xea\xf3\xb8\x49\xe7\xd2\x2d\x87\x6a\x7e\x21\xf0\x01\x44\x6c\x01\x67\xfc\xdc\x70\xd9\x8b\xe1\x06\x24\x09\x77\x9f\x23\xd0\x4f\xf7\x6d\xf3\x39\x13\x55\xcd\x7f\x84\xa4\x1b\xbb\x5d\x48\xac\xba\xef\xfa\x62\x49\x18\xd7\x50\x8c\xac\x52\xa1\x5c\xf7\x0d\x02\xa4\xc7\xae\x67\xd0\xee\x74\x93\x55\xac\xf0\x39\xe8\x04\xd8\xd6\x8d\x37\x53\x5f\x5c\x08\x93\xab\xfa\xc7\x28\xd3\x05\x0f\x10\x01\x69\x7c\xec\xc5\xb2\x79\x66\x37\xec\x62\x0d\x1c\x22\x18\x49\x08\x07\xbb\x57\x91\x23\x6a\x98\x1f\x3e\x44\xfa\x84\x0b\xf7\xb7\x59\x5c\x0f\xaa\x97\x66\x7c\x8f\x1c\x20\x75\x66\xa5\x09\x37\x17\x23\x9b\x40\xb4\xfd\xe0\x16\x9e\xe3\xc2\x9f\x2b\x9a\x0c\x61\x06\x18\x67\x42\x4d\xa2\xeb\xe7\x94\xfb\x76\xeb\x9b\xab\x3d\x17\xf8\x80\x09\x94\x30\x3e\xb5\x6c\x19\x23\x80\xe8\x28\xf5\x08\x40\x45\xc6\x28\x39\x46\x40\x4a\x85\x44\xf0\x40\x21\x21\xaf\x05\x56\xc7\x8c\x71\xb5\x78\x9f\x21\xf7\x55\x26\xf1\x37\x18\x5b\xf3\x9c\xef\x3b\x42\x1b\x8b\x21\x6b\x42\x72\xa5\x41\x7d\x29\x29\x1c\xc6\x8e\x44\x18\x4c\x54\xe1\x14\x95\x4a\x56\x8b\x3c\xf6\x80\xad\xf7\x44\xa2\x84\xd8\x23\xbc\x76\xb7\x71\xd8\xcc\x03\x97\x97\x00\x4f\x0a\x5d\x67\xc2\x50\x55\xb6\x7f\x9b\x79\xe5\xe4\x0c\x7d\x79\x94\xb9\xba\xae\x5b\x93\xaa\xc0\x34\x63\x1c\x68\x30\x36\xa4\x62\x3c\x2b\x05\xca\x21\xe3\x20\x30\x73\xaa\x62\x6d\x46\x7a\x51\x0b\xa4\xf7\x9f\x92\x91\xb8\xa4\x88\x84\xc2\x4c\x55\x7c\x77\xe5\xb1\x52\xa9\x70\xb0\xd7\x04\x57\xd8\x1f\x34\x0e\xaf\x8d\xe8\x00\xda\xea\xef\x2e\xfa\x33\x05\xff\xcc\x29\xa6\x0a\x4a\xed\x26\x53\xa7\x9a\xe9\x39\xe7\x5b\xce\x88\x5e\x73\x8f\xc4\xd8\xa0\x33\x7c\x24\x6d\x60\xee\x94\x1b\xc1\xd5\x89\x3a\xf9\x1a\xdd\x75\x34\xf4\xd6\x1d\x23\x1b\x27\xfc\x45\xc5\xdc\x66\x63\xe3\xad\xa7\xee\xa0\xaa\x65\xf0\x58\xd0\xc0\x50\x39\xd7\xd2\x0e\xa0\xc6\xd0\x7e\xd1\x6a\xa1\xdb\x64\x1d\x04\x05\x76\x73\xbb\xb2\x24\xbb\x60\xec\x6e\x9d\x58\x7b\x02\xae\x79\xb2\x09\x1a\x9c\xbf\xcf\xcf\xb6\x3b\x20\xef\xdc\x19\x4b\xb4\xb5\x26\xae\xae\xe0\xd6\xde\x28\x0e\xe1\x1c\x23\x40\x09\x6c\xd9\xa5\x4f\xd4\x66\x3e\x01\xf9\x3a\xc7\x46\x0a\x57\xc0\x6a\x77\xc1\x5b\x99\xfe\xdd\x21\xa5\xc6\x5c\x3e\x60\x54\x03\xd2\xb6\xe9\xe3\x60\xd4\xbe\xbb\x0c\x1a\x2e\x26\x48\x04\x70\x82\x73\x24\x43\x89\xe8\x86\xf1\x44\xcd\x0b\xa4\x20\x6b\xef\x65\x2f\x4a\xfd\x33\x39\x9f\x23\x81\x08\x01\x82\x65\x15\x93\x43\xd3\x02\x08\x3a\x5e\x55\x3e\x1b\xf4\x1d\xc2\xa4\x16\x90\xa1\x5c\x75\x57\xbf\x01\x9f\x4b\x2b\x46\xb1\x62\xce\x0c\x11\xb7\x65\x85\x9e\xb3\x7e\xdb\x06\x24\xd4\xd9\x8c\x9b\xfa\xd8\xc9\x82\xe1\x09\x6d\xe3\x77\x59\x75\x9e\x31\xd1\xb9\xd6\x7b\x3c\xa6\xdf\x71\x22\xba\x00\xa9\x33\xc9\x30\xf8\x09\xe2\x07\x4b\x4b\x77\xca\xc8\x38\x23\x38\x3f\x2e\x25\x61\xce\x68\xab\xe4\x18\x87\xb8\xd1\x03\xb5\x3b\xe8\x56\xa8\xe2\x2a\x18\xac\x0d\xc2\x57\x4c\x0b\xf6\xf5\x82\x0d\x97\x73\x25\x4e\x50\x0e\x56\xbe\xbb\x55\xd1\x52\x09\x84\xa9\xba\xb8\x9c\xdf\x2a\xd6\x0d\xd5\x7c\xf0\xcf\x40\xd6\x1f\xe0\xc2\xf7\xe8\x9e\x4c\x9f\xf3\x3a\x38\x0d\xac\xa0\x62\xc2\xe9\x80\x0b\x3c\xf4\x08\x89\xd8\x83\x2d\x50\xd5\xa2\xc6\xc7\x1d\x54\xc6\xf8\xf2\xa7\x8d\xf0\x88\x78\x13\x4e\x48\x98\xa3\x6a\xa9\xe8\x88\x1e\xa8\xa7\xce\x1a\x9c\xcc\xcf\x2d\x12\xff\xec\x22\xc4\x75\x98\xf7\x0e\x42\xd6\x5b\xea\x19\x21\x4c\x4f\x19\xae\x5b\xfe\xf8\x63\xca\xc9\x7f\x28\xb9\x2d\xe9\xf5\x77\x61\x1e\xab\x3e\x0e\x3d\xf3\x7a\xd0\xd5\x26\xda\xc4\xde\x8b\xa8\xe5\xf8\x6f\xda\x77\x7b\x44\xe0\xea\xf3\x2f\xec\x04\x6f\x48\x2e\xdd\x8b\xa6\x40\x6e\xe9\xa0\xfe\x4f\x2d\xff\x11\x47\xfc\x79\xfe\xd5\x3d\x20\x0b\xbe\xdc\x6a\xa0\xae\x2e\xce\x11\xcf\x95\x5e\x81\xcd\x5e\xda\x14\xe3\xc1\xa2\x61\x92\xe9\x99\x7f\x4e\x93\xd1\xf7\x69\x1d\xc6\x66\xcc\x86\x0d\xe6\x78\xe3\x3b\xae\x90\x73\x83\xa4\x1e\xc4\x73\xbf\x62\x6d\xda\x29\x71\x5e\xf2\x05\x93\xcd\xfd\xbb\x99\x3e\x60\xee\xde\xfb\x07\x15\xd0\x05\x86\x74\x6e\x9b\x5a\x87\x87\x5e\xbb\xd3\x77\x9b\x9e\xf8\x37\xf0\x27\xaf\x38\xb5\x9c\xf4\x38\x99\x49\x7d\x1f\x0f\x5a\xdb\x17\x98\x9b\x91\x7e\x2c\x90\xf6\x15\x89\x91\xdd\x37\xe6\x79\xca\x67\x46\xe7\xdb\x4e\x7b\xcc\xdb\xbf\xb1\xf4\xdc\x6a\xac\xcc\xbf\xcd\x7b\xd8\xd5\x69\xf5\x6f\x00\x00\x00\xff\xff\xfc\xf3\x11\x6a\x88\x2f\x00\x00") +var _dataConfig_schema_v31Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x1a\xcb\x8e\xdb\x36\xf0\xee\xaf\x10\x94\xdc\xe2\xdd\x4d\xd1\xa0\x40\x73\xeb\xb1\xa7\xf6\xdc\x85\x23\xd0\xd2\x58\x66\x96\x22\x19\x92\x72\xd6\x09\xfc\xef\x05\xf5\x32\x45\x91\x22\x6d\x2b\xd9\x45\xd1\xd3\xae\xc5\x99\xe1\xbc\x67\x38\xe4\xf7\x55\x92\xa4\x6f\x65\xbe\x87\x0a\xa5\x1f\x93\x74\xaf\x14\xff\xf8\xf0\xf0\x59\x32\x7a\xd7\x7e\xbd\x67\xa2\x7c\x28\x04\xda\xa9\xbb\xf7\x1f\x1e\xda\x6f\x6f\xd2\xb5\xc6\xc3\x85\x46\xc9\x19\xdd\xe1\x32\x6b\x57\xb2\xc3\xaf\xf7\xbf\xdc\x6b\xf4\x16\x44\x1d\x39\x68\x20\xb6\xfd\x0c\xb9\x6a\xbf\x09\xf8\x52\x63\x01\x1a\xf9\x31\x3d\x80\x90\x98\xd1\x74\xb3\x5e\xe9\x35\x2e\x18\x07\xa1\x30\xc8\xf4\x63\xa2\x99\x4b\x92\x01\xa4\xff\x60\x90\x95\x4a\x60\x5a\xa6\xcd\xe7\x53\x43\x21\x49\x52\x09\xe2\x80\x73\x83\xc2\xc0\xea\x9b\x87\x33\xfd\x87\x01\x6c\x6d\x53\x35\x98\x6d\xbe\x73\xa4\x14\x08\xfa\xf7\x94\xb7\x66\xf9\xd3\x23\xba\xfb\xf6\xc7\xdd\x3f\xef\xef\x7e\xbf\xcf\xee\x36\xef\xde\x8e\x96\xb5\x7e\x05\xec\xda\xed\x0b\xd8\x61\x8a\x15\x66\x74\xd8\x3f\x1d\x20\x4f\xdd\x7f\xa7\x61\x63\x54\x14\x0d\x30\x22\xa3\xbd\x77\x88\x48\x18\xcb\x4c\x41\x7d\x65\xe2\x29\x24\xf3\x00\xf6\x42\x32\x77\xfb\x3b\x64\x1e\x8b\x73\x60\xa4\xae\x82\x16\xec\xa1\x5e\x48\x98\x76\xfb\x65\xec\x27\x21\x17\xa0\xc2\x2e\xdb\x42\xbd\x98\xc7\xea\xed\x6f\x13\x78\xd5\x0b\x3d\x0b\xdb\x42\x18\x7b\x37\x0c\x8e\xc2\xdb\xa5\x2a\x57\x78\xf9\x75\x35\x28\xcb\xa3\xa5\x02\x38\x61\x47\xfd\xcd\xa3\x8f\x16\xa0\x02\xaa\xd2\x41\x05\x49\x92\x6e\x6b\x4c\x0a\x5b\xa3\x8c\xc2\x5f\x9a\xc4\xa3\xf1\x31\x49\xbe\xdb\x99\xcc\xa0\xd3\xac\x8f\x7e\xf9\x0d\x3e\xac\x7b\x64\x19\xd6\x73\x46\x15\x3c\xab\x46\xa8\xf9\xad\x5b\x15\xb0\xfc\x09\xc4\x0e\x13\x88\xc5\x40\xa2\x94\x33\x2a\x23\x58\xaa\x8c\x89\xac\xc0\xb9\x4a\x4f\x16\xfa\x84\x5e\xd8\x9f\x06\x54\xe3\xd7\x66\xe5\x20\x98\xe6\x88\x67\xa8\x28\x46\x72\x20\x21\xd0\x31\x5d\x27\x29\x56\x50\x49\xb7\x88\x49\x5a\x53\xfc\xa5\x86\x3f\x3b\x10\x25\x6a\xb0\xe9\x16\x82\xf1\xe5\x09\x97\x82\xd5\x3c\xe3\x48\x68\x07\x9b\x57\x7f\x9a\xb3\xaa\x42\x74\x29\xaf\xbb\x44\x8e\x08\xcd\x33\xaa\x10\xa6\x20\x32\x8a\xaa\x90\x23\xe9\xa8\x03\x5a\xc8\xac\x2d\xf8\xb3\x6e\xb4\xcb\x5a\x7c\x69\x11\x18\xaa\xff\xa2\xf6\x28\xe8\x9c\x63\xb7\x64\xb4\x6b\x6b\xde\x52\x0b\x31\x93\x80\x44\xbe\xbf\x12\x9f\x55\x08\xd3\x18\xdd\x01\x55\xe2\xc8\x19\x6e\xfd\xe5\xd5\x39\x02\xd0\x43\x36\xe4\x92\x8b\xd5\x00\xf4\x80\x05\xa3\x55\x1f\x0d\x31\x09\x66\x48\xf2\x1a\xff\x99\x33\x09\xb6\x62\x2c\x01\xcd\xa5\x41\xd4\x91\x4e\x7a\x8c\xc7\x5e\xf0\x75\x92\xd2\xba\xda\x82\xd0\x3d\xec\x08\x72\xc7\x44\x85\x34\xb3\xfd\xde\xc6\xf2\x48\xd3\x0e\xcf\x33\x15\x68\xca\xa0\xcb\x3a\x22\x19\xc1\xf4\x69\x79\x17\x87\x67\x25\x50\xb6\x67\x52\xc5\xe7\x70\x03\x7d\x0f\x88\xa8\x7d\xbe\x87\xfc\x69\x06\xdd\x84\x1a\x61\x33\xa9\x62\x9c\x1c\x57\xa8\x0c\x03\xf1\x3c\x04\x42\xd0\x16\xc8\x55\x72\x2e\xaa\x7c\x83\x2c\x2b\x4b\x0d\xea\xf3\xb8\x49\xe7\xd2\x2d\x87\x6a\x7e\x21\xf0\x01\x44\x6c\x01\x67\xfc\xdc\x70\xd9\x8b\xe1\x06\x24\x09\x77\x9f\x23\xd0\x4f\xf7\x6d\xf3\x39\x13\x55\xcd\x7f\x84\xa4\x1b\xbb\x5d\x48\xac\xba\xef\xfa\x62\x49\x18\xd7\x50\x8c\xac\x52\xa1\x5c\xf7\x0d\x02\xa4\xc7\xae\x67\xd0\xee\x74\x93\x55\xac\xf0\x39\xe8\x04\xd8\xd6\x8d\x37\x53\x5f\x5c\x08\x93\xab\xfa\xc7\x28\xd3\x05\x0f\x10\x01\x69\x7c\xec\xc5\xb2\x79\x66\x37\xec\x62\x0d\x1c\x22\x18\x49\x08\x07\xbb\x57\x91\x23\x6a\x98\x1f\x3e\x44\xfa\x84\x0b\xf7\xb7\x59\x5c\x0f\xaa\x97\x66\x7c\x8f\x1c\x20\x75\x66\xa5\x09\x37\x17\x23\x9b\x40\xb4\xfd\xe0\x16\x9e\xe3\xc2\x9f\x2b\x9a\x0c\x61\x06\x18\x67\x42\x4d\xa2\xeb\xe7\x94\xfb\x76\xeb\x9b\xab\x3d\x17\xf8\x80\x09\x94\x30\x3e\xb5\x6c\x19\x23\x80\xe8\x28\xf5\x08\x40\x45\xc6\x28\x39\x46\x40\x4a\x85\x44\xf0\x40\x21\x21\xaf\x05\x56\xc7\x8c\x71\xb5\x78\x9f\x21\xf7\x55\x26\xf1\x37\x18\x5b\xf3\x9c\xef\x3b\x42\x1b\x8b\x21\x6b\x42\x72\xa5\x41\x7d\x29\x29\x1c\xc6\x8e\x44\x18\x4c\x54\xe1\x14\x95\x4a\x56\x8b\x3c\xf6\x80\xad\xf7\x44\xa2\x84\xd8\x23\xbc\x76\xb7\x71\xd8\xcc\x03\x97\x97\x00\x4f\x0a\x5d\x67\xc2\x50\x55\xb6\x7f\x9b\x79\xe5\xe4\x0c\x7d\x79\x94\xb9\xba\xae\x5b\x93\xaa\xc0\x34\x63\x1c\x68\x30\x36\xa4\x62\x3c\x2b\x05\xca\x21\xe3\x20\x30\x73\xaa\x62\x6d\x46\x7a\x51\x0b\xa4\xf7\x9f\x92\x91\xb8\xa4\x88\x84\xc2\x4c\x55\x7c\x77\xe5\xb1\x52\xa9\x70\xb0\xd7\x04\x57\xd8\x1f\x34\x0e\xaf\x8d\xe8\x00\xda\xea\xef\x2e\xfa\x33\x05\xff\xcc\x29\xa6\x0a\x4a\xed\x26\x53\xa7\x9a\xe9\x39\xe7\x5b\xce\x88\x5e\x73\x8f\xc4\xd8\xa0\x33\x7c\x24\x6d\x60\xee\x94\x1b\xc1\xd5\x89\x3a\xf9\x1a\xdd\x75\x34\xf4\xd6\x1d\x23\x1b\x27\xfc\x45\xc5\xdc\x66\x63\xe3\xad\xa7\xee\xa0\xaa\x65\xf0\x58\xd0\xc0\x50\x39\xd7\xd2\x0e\xa0\xc6\xd0\x7e\xd1\x6a\xa1\xdb\x64\x1d\x04\x05\x76\x73\xbb\xb2\x24\xbb\x60\xec\x6e\x9d\x58\x7b\x02\xae\x79\xb2\x09\x1a\x9c\xbf\xcf\xcf\xb6\x3b\x20\xef\xdc\x19\x4b\xb4\xb5\x26\xae\xae\xe0\xd6\xde\x28\x0e\xe1\x1c\x23\x40\x09\x6c\xd9\xa5\x4f\xd4\x66\x3e\x01\xf9\x3a\xc7\x46\x0a\x57\xc0\x6a\x77\xc1\x5b\x99\xfe\xdd\x21\xa5\xc6\x5c\x3e\x60\x54\x03\xd2\xb6\xe9\xe3\x60\xd4\xbe\xbb\x0c\x1a\x2e\x26\x48\x04\x70\x82\x73\x24\x43\x89\xe8\x86\xf1\x44\xcd\x0b\xa4\x20\x6b\xef\x65\x2f\x4a\xfd\x33\x39\x9f\x23\x81\x08\x01\x82\x65\x15\x93\x43\xd3\x02\x08\x3a\x5e\x55\x3e\x1b\xf4\x1d\xc2\xa4\x16\x90\xa1\x5c\x75\x57\xbf\x01\x9f\x4b\x2b\x46\xb1\x62\xce\x0c\x11\xb7\x65\x85\x9e\xb3\x7e\xdb\x06\x24\xd4\xd9\x8c\x9b\xfa\xd8\xc9\x82\xe1\x09\x6d\xe3\x77\x59\x75\x9e\x31\xd1\xb9\xd6\x7b\x3c\xa6\xdf\x71\x22\xba\x00\xa9\x33\xc9\x30\xf8\x09\xe2\x2f\xaa\x05\x7d\x1c\xc9\x38\x23\x38\x3f\x2e\xa5\x8a\x9c\xd1\x96\x8f\x18\xcf\xb9\xd1\x55\xb5\xdf\xe8\x9e\xa9\xe2\x2a\x18\xd5\x0d\xc2\x57\x4c\x0b\xf6\xf5\x82\x0d\x97\xd3\x36\x27\x28\x07\x2b\x31\xde\xaa\x68\xa9\x04\xc2\x54\x5d\x5c\xf7\x6f\x15\xeb\x86\xb2\x3f\x38\x72\xa0\x3c\x0c\x70\xe1\x0b\x77\x4f\x49\xc8\x79\x1d\x1c\x1b\x56\x50\x31\xe1\x74\xc0\x05\x5e\x84\x84\x44\xec\xc1\x16\x28\x7f\x51\x73\xe6\x0e\x2a\x63\x7c\xf9\x63\x49\x78\x96\xbc\x09\x37\xc5\x98\xa3\x6a\xa9\xe8\x88\x9e\xbc\xa7\xce\x62\x9d\xcc\x0f\x38\x12\xff\x90\x23\xc4\x75\x98\xf7\x0e\x42\xd6\x5b\xea\x99\x35\x4c\x8f\x23\xae\xe7\x00\xf1\xe7\x99\x93\xff\xf4\x72\x5b\xd2\xeb\x2f\xcd\x3c\x56\x7d\x1c\x9a\xeb\xf5\xa0\xab\x4d\xb4\x89\xbd\x37\x56\xcb\xf1\xdf\xf4\xf9\xf6\x2c\xc1\x75\x20\xb8\xb0\x65\xbc\x21\xb9\x74\x4f\x9f\x02\xb9\xa5\x83\xfa\x3f\xb5\xfc\x47\x1c\xf1\xe7\xf9\x57\xf7\xd2\x2c\xf8\xc4\xab\x81\xba\xba\x38\x47\xbc\x6b\x7a\x05\x36\x7b\x69\x53\x8c\x27\x90\x86\x49\xa6\xc3\x81\x39\x4d\x46\x5f\xbc\x75\x18\x9b\x31\x1b\x36\x98\xe3\x31\xf0\xb8\x42\xce\x4d\x9c\x7a\x10\xcf\x45\x8c\xb5\x69\xa7\xc4\x79\xc9\x17\x4c\x36\xf7\xef\x66\xfa\x80\xb9\x0b\xf2\x1f\x54\x40\x17\x98\xe6\xb9\x6d\x6a\x1d\x1e\x7a\xed\x4e\x1f\x78\x7a\xe2\xdf\xc0\x9f\x3c\xf7\xd4\x72\xd2\xe3\x64\x78\xf5\x7d\x3c\x91\x6d\x9f\x6a\x6e\x46\xfa\xb1\x40\xda\xe7\x26\x46\x76\xdf\x98\xe7\x29\x9f\x19\x9d\x8f\x40\xed\x79\x70\xff\x18\xd3\x73\xfd\xb1\x32\xff\x36\x0f\x67\x57\xa7\xd5\xbf\x01\x00\x00\xff\xff\x1c\xc9\xf2\x49\xb1\x2f\x00\x00") func dataConfig_schema_v31JsonBytes() ([]byte, error) { return bindataRead( @@ -112,7 +112,7 @@ func dataConfig_schema_v31Json() (*asset, error) { return a, nil } -var _dataConfig_schema_v32Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x1b\x4d\x73\xdc\xa8\xf2\x3e\xbf\x42\xa5\xe4\x16\x7f\xa4\xde\x4b\xbd\xaa\x97\xdb\x3b\xbe\xd3\xee\x79\x5d\x13\x15\x83\x7a\x34\xc4\x12\x10\x40\x63\x4f\x52\xfe\xef\x5b\xfa\x1c\x40\x20\xd0\x8c\x1c\x67\xb7\xf6\x64\x5b\x74\x37\xf4\x77\x37\x8d\x7f\x6c\x92\x24\x7d\x2f\xf1\x01\x2a\x94\x7e\x4e\xd2\x83\x52\xfc\xf3\xfd\xfd\x57\xc9\xe8\x6d\xf7\xf5\x8e\x89\xe2\x3e\x17\x68\xaf\x6e\x3f\x7e\xba\xef\xbe\xbd\x4b\x6f\x1a\x3c\x92\x37\x28\x98\xd1\x3d\x29\xb2\x6e\x25\x3b\xfe\xfb\xee\x5f\x77\x0d\x7a\x07\xa2\x4e\x1c\x1a\x20\xb6\xfb\x0a\x58\x75\xdf\x04\x7c\xab\x89\x80\x06\xf9\x21\x3d\x82\x90\x84\xd1\x74\x7b\xb3\x69\xd6\xb8\x60\x1c\x84\x22\x20\xd3\xcf\x49\x73\xb8\x24\x19\x41\x86\x0f\x1a\x59\xa9\x04\xa1\x45\xda\x7e\x7e\x69\x29\x24\x49\x2a\x41\x1c\x09\xd6\x28\x8c\x47\x7d\x77\x7f\xa6\x7f\x3f\x82\xdd\xd8\x54\xb5\xc3\xb6\xdf\x39\x52\x0a\x04\xfd\x7d\x7a\xb6\x76\xf9\xcb\x03\xba\xfd\xfe\xbf\xdb\x3f\x3e\xde\xfe\xf7\x2e\xbb\xdd\x7e\x78\x6f\x2c\x37\xf2\x15\xb0\xef\xb6\xcf\x61\x4f\x28\x51\x84\xd1\x71\xff\x74\x84\x7c\xe9\x7f\x7b\x19\x37\x46\x79\xde\x02\xa3\xd2\xd8\x7b\x8f\x4a\x09\x26\xcf\x14\xd4\x13\x13\x8f\x21\x9e\x47\xb0\x37\xe2\xb9\xdf\xdf\xc1\xb3\xc9\xce\x91\x95\x75\x15\xd4\xe0\x00\xf5\x46\xcc\x74\xdb\xaf\xa3\x3f\x09\x58\x80\x0a\x9b\x6c\x07\xf5\x66\x16\xdb\x6c\x7f\x1d\xc3\x9b\x81\xe9\x59\xd8\x0e\x42\xdb\xbb\x3d\xa0\xe1\xde\x2e\x51\xb9\xdc\xcb\x2f\xab\x51\x58\x1e\x29\xe5\xc0\x4b\x76\x6a\xbe\x79\xe4\xd1\x01\x54\x40\x55\x3a\x8a\x20\x49\xd2\x5d\x4d\xca\xdc\x96\x28\xa3\xf0\x5b\x43\xe2\x41\xfb\x98\x24\x3f\xec\x48\xa6\xd1\x69\xd7\x8d\xbf\xfc\x0a\x1f\xd7\x3d\xbc\x8c\xeb\x98\x51\x05\xcf\xaa\x65\x6a\x7e\xeb\x4e\x04\x0c\x3f\x82\xd8\x93\x12\x62\x31\x90\x28\xe4\x8c\xc8\x4a\x22\x55\xc6\x44\x96\x13\xac\x9c\xf8\x18\xe1\x03\x64\x7b\xc1\xaa\x20\x95\x7d\xd6\x9d\x43\xa6\x2f\x16\x9d\x09\xe1\xb0\x61\x8e\xa8\xda\x5f\xdb\x8d\x83\x60\x8a\x11\xcf\x50\x9e\x1b\x02\x41\x42\xa0\x53\x7a\x93\xa4\x44\x41\x25\xdd\xb2\x4a\xd2\x9a\x92\x6f\x35\xfc\xbf\x07\x51\xa2\x06\x9b\x6e\x2e\x18\x5f\x9f\x70\x21\x58\xcd\x33\x8e\x44\x63\xa9\xf3\x7a\x4c\x31\xab\x2a\x44\xd7\x32\xdf\x25\x7c\x44\x48\x9e\x51\x85\x08\x05\x91\x51\x54\x85\x2c\xb2\x71\x5f\xa0\xb9\xcc\xba\xca\x21\xd6\x92\x0c\x02\x63\x19\xb1\xaa\x3e\x72\x3a\xe7\x21\x1d\x99\xc6\x47\x9a\xb3\xa5\x16\x62\x26\x01\x09\x7c\xb8\x10\x9f\x55\x88\xd0\x18\xd9\x01\x55\xe2\xc4\x19\xe9\xec\xe5\x97\x33\x04\xa0\xc7\x6c\x0c\x4a\x8b\xc5\x00\xf4\x48\x04\xa3\xd5\xe0\x0d\x71\x91\x4a\xc3\x7f\xe6\x4c\x82\x2d\x18\x8b\x41\x7d\x69\x64\xd5\x90\xc9\x80\xf1\x30\x30\x7e\x93\xa4\xb4\xae\x76\x20\x9a\x62\xd8\x80\xdc\x33\x51\xa1\xe6\xb0\xc3\xde\xda\xb2\x21\x69\x87\xe5\xe9\x02\xd4\x79\x68\xea\x03\x54\x66\x25\xa1\x8f\xeb\x9b\x38\x3c\x2b\x81\xb2\x03\x93\xea\x92\x64\x90\x1e\x00\x95\xea\x80\x0f\x80\x1f\x67\xd0\x75\x28\x03\x9b\x49\x15\x63\xe4\xa4\x42\x45\x18\x88\xe3\x10\x48\x89\x76\x50\x5e\xc4\xe7\xaa\xc2\xd7\xc8\xb2\xa2\x68\x40\x7d\x16\x37\x29\x81\xfa\xe5\x50\xf1\x90\x0b\x72\x04\x11\x5b\x09\x30\x7e\xae\xdc\xec\xc5\x70\x25\x93\x84\xcb\x58\x03\xf4\xcb\x5d\x57\xc5\xce\x78\x55\xfb\x5b\x59\xa6\x5b\xbb\x5c\x48\xac\xbc\xef\xfa\x62\x71\x18\x57\x50\x18\x5a\xa9\x10\x6e\xea\x06\x01\xd2\xa3\xd7\x33\x68\xdf\x26\x65\x15\xcb\x7d\x06\x3a\x01\xb6\x65\xe3\x8d\xd4\x8b\x13\x61\x72\x51\x21\x1a\xa5\xba\x60\x27\x12\xe0\xc6\x77\xbc\xd8\x63\x9e\x8f\x1b\x36\xb1\x16\x0e\x95\x04\x49\x08\x3b\xbb\x57\x90\x06\x35\xc2\x8f\x9f\x22\x6d\xc2\x85\xfb\x9f\x59\x5c\x0f\xaa\x97\x66\x7c\x8d\x1c\x20\x75\x3e\x4a\xeb\x6e\xae\x83\x6c\x03\xde\xf6\xca\x25\x3c\x27\xb9\x3f\x56\xb4\x11\x42\x77\x30\xce\x84\x9a\x78\xd7\xf2\x74\xef\xb3\x60\x5d\x5c\x43\x9c\x3a\x27\xfc\x6e\xf3\x89\x34\x26\xea\x8e\x42\x9a\xfa\x5f\xd0\x3f\xc2\x9e\x91\xce\x44\x29\x07\xb4\x42\xa2\x00\xb3\x0d\x21\x54\x41\x01\xc2\x83\xc0\xeb\x5d\x49\xe4\x01\xf2\x25\x38\x82\x29\x86\x59\x19\xe7\x18\xce\x3e\x36\xde\x19\x4c\x82\xdb\xab\x6b\x33\x2e\xc8\x91\x94\x50\x58\x1c\xef\x18\x2b\x01\x51\x23\x51\x08\x40\x79\xc6\x68\x79\x8a\x80\x94\x0a\x89\x60\xfb\x27\x01\xd7\x82\xa8\x53\xc6\xb8\x5a\xbd\x2a\x94\x87\x2a\x93\xe4\x3b\x98\xbe\x77\xb6\xfa\x9e\xd0\xd6\x3a\x90\x75\x31\x96\xbc\x96\xfb\xf9\xcc\xf6\x95\xdc\x46\xb2\x5a\xe0\xeb\x1c\x67\x16\xbe\x36\x83\xdc\x3c\x70\xb1\x04\x78\xe2\xf0\xbd\x0a\x43\x35\xd4\xac\xab\x38\x03\xb5\x3c\x49\xac\x2e\xab\xad\xa5\xca\x09\xcd\x18\x07\x1a\xf4\x0d\xa9\x18\xcf\x0a\x81\x30\x64\x1c\x04\x61\x4e\x51\x18\x01\x36\xaf\x05\x6a\xf6\x9f\x92\x91\xa4\xa0\xc8\x1d\x77\x34\x50\x55\xf1\xfd\x85\x97\x00\x4a\x85\x9d\xbd\x2e\x49\x45\xfc\x4e\xe3\xb0\xda\x88\x7a\xad\xab\xd5\xdc\x25\xda\x4c\x79\x16\x15\xb2\x67\x3a\x84\xf9\x06\x21\xa2\x33\x38\x20\xb1\x20\x75\xb4\x8e\xb9\xf7\xe4\x27\x57\xdf\xe0\x3c\x97\x31\xe2\x6a\xe9\xdd\xf4\x07\xd9\x3a\xe1\x17\x95\x5e\xf6\x31\xb6\xde\xea\xc7\xed\x54\xb5\x0c\x36\x71\x2d\x0c\x95\x73\x0d\xc8\x08\x3a\x9d\xd5\x24\x7f\x89\x08\x6d\xe8\xa8\x05\x77\xe8\x26\x22\x8e\xf7\x3b\x45\xc6\xce\xd7\x8e\xfa\xd1\x15\x81\x86\x83\x19\x95\x44\x2a\xa0\xf8\x14\xbf\xd1\x8e\x4c\x6e\x89\xa7\x42\x99\xef\xbb\xe2\xba\xae\x16\x0a\x15\x5d\xbc\x8d\x6e\x74\xe2\x7d\xb5\x1f\xe3\xfd\x14\x56\x28\xc3\x8c\x7b\x54\x13\xcf\xc6\xd2\x34\x6b\x5d\x5d\xcc\xd4\xa1\xbe\x90\xf1\xc4\xc4\x63\x93\x90\x72\xe2\x8e\x1c\x1b\x0b\x65\xc1\xe4\xd3\xba\xeb\x1b\x08\xb8\x46\x7a\x3a\x68\x70\x04\x3a\x3f\x5e\xec\x81\xbc\xa3\x3f\x22\xd1\xce\x1a\x7a\xb9\x12\x6d\x93\x19\xc4\x31\x9c\xef\x05\x28\x41\xac\x51\xc2\x50\x34\xe9\xb9\x1d\xe4\xaf\x79\xe1\xae\x48\x05\xac\x76\x87\xa1\x8d\x6e\x38\x3d\x52\xaa\x8d\x46\x03\x4a\xd5\x20\x6d\x9d\x3e\x8c\x4a\x1d\xfa\xf2\xa0\xe2\x62\x12\x16\xd0\xbc\x1d\x6d\x44\x65\x37\x01\xbc\x24\x18\xc9\x50\x05\x71\xc5\x2d\x70\xcd\x73\xa4\x20\xeb\xde\xd1\x2c\xaa\xd9\x66\x8a\x35\x8e\x04\x2a\x4b\x28\x89\xac\x62\x8a\x9f\x34\x87\x12\x39\xa3\x7f\xb0\xee\x6d\xd1\xf7\x88\x94\xb5\x80\x0c\x61\x6f\x98\xb6\x30\x2a\x46\x89\x62\xce\x70\x12\xb7\x65\x85\x9e\xb3\x61\xdb\x16\x24\xd4\x92\x98\xdd\x78\xec\x05\xae\x66\x09\x5d\xee\x5e\x56\x56\xcf\xa8\xe8\x5c\xa4\x7b\x2c\x66\xd8\x71\xc2\xba\x00\xd9\x84\x9d\xf1\x7e\x3d\x88\x1f\x0c\xf0\xfd\xf5\x40\xc6\x59\x49\xba\x2a\x60\x0d\x0e\x31\xa3\x9d\x90\x63\x0c\xe2\x4a\x0b\x6c\xcc\xa1\xe9\x61\x2a\xae\x82\xce\xda\x22\x3c\x11\x9a\xb3\xa7\x05\x1b\xae\x67\x4a\xbc\x44\x18\xac\xe0\x78\xad\xa0\xa5\x12\x88\x50\xb5\x78\x9c\x74\x2d\x5b\x57\xa4\xfe\xd1\x3e\x03\x29\x62\x84\x0b\x26\x7d\x5f\x5a\xc0\xbc\x0e\x0e\x5d\x2a\xa8\x98\x70\x17\xc0\x57\xf0\x38\xbc\x78\x0b\xb0\x38\x80\xad\x90\x02\xa3\xa6\x74\x3d\x54\xc6\xf8\xfa\xd7\x04\xe1\x49\xdc\x36\x1c\x90\x08\x47\xd5\x5a\xde\x11\x3d\xb7\x4c\x9d\x39\x38\x99\x6f\x67\x13\x7f\x4b\x1b\x3a\x75\xf8\xec\x3d\x84\xac\x77\xd4\xd3\x05\x4e\x9b\x81\x35\x6f\xb3\x57\x0c\x7a\xc3\x93\x03\x8f\x56\x1f\xc6\x02\xfb\x66\x94\xd5\x36\x5a\xc5\xde\x79\xff\x7a\xe7\x6f\x6b\x7d\xfb\x6e\xcf\xd5\x14\x20\xa5\x10\x3e\x44\xf5\x0f\x0b\x8b\xc6\x2b\xe2\xd0\xa4\xcb\x75\x86\xa1\x1e\xea\x9f\x28\xf4\x37\xb1\xd9\x9f\x67\x5f\xfd\xdb\xe0\xe0\xa3\xdc\x16\xea\xe2\x3c\x1e\xf1\x12\xf5\x17\xd0\xd9\x5b\xab\xc2\x1c\x1e\x68\x2a\x99\xde\x25\xcc\x49\x72\xe9\xeb\xdb\xad\x79\x0c\x1b\xcc\xf1\xef\x1b\x66\x32\x9d\x1b\x2d\x0e\x20\x9e\xbb\x2b\x6b\xd3\x5e\x88\xf3\x9c\xaf\x18\x6c\xee\x3e\xcc\x94\x0c\x73\x2f\x91\x5e\x29\xd7\xae\x30\xb6\x75\xeb\xd4\xea\x33\x06\xe9\x4e\x9f\xe4\x7b\xfc\x5f\xc3\x9f\x3c\xd0\x6f\xf8\xa4\xa7\xc9\x5d\xd7\x0f\xf3\xa2\xbe\x7b\x5c\xbf\x35\xe4\x63\x81\x74\xef\xfa\xb4\xe8\xbe\xd5\x5b\x2f\x9f\x1a\x9d\xcf\xf6\xed\x31\xc1\xf0\x7c\xde\x33\xb9\xdc\xe8\x3f\xdb\x7f\x75\xd8\xbc\x6c\xfe\x0c\x00\x00\xff\xff\xa9\x16\x7b\x3d\x63\x35\x00\x00") +var _dataConfig_schema_v32Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5b\xcd\x73\xdc\x28\x16\xbf\xf7\x5f\xa1\x52\x72\x8b\x3f\x52\xbb\xa9\xad\xda\xdc\xf6\xb8\xa7\x99\xf3\xb8\x3a\x2a\x1a\xbd\x56\x13\x4b\x40\x00\xb5\xdd\x49\xf9\x7f\x9f\xd2\x67\x03\x02\x81\xba\xe5\x38\x33\x35\x27\xdb\xe2\xf7\x80\xf7\xfd\x1e\xe0\x1f\x9b\x24\x49\xdf\x4b\x7c\x80\x0a\xa5\x9f\x93\xf4\xa0\x14\xff\x7c\x7f\xff\x55\x32\x7a\xdb\x7d\xbd\x63\xa2\xb8\xcf\x05\xda\xab\xdb\x8f\x9f\xee\xbb\x6f\xef\xd2\x9b\x86\x8e\xe4\x0d\x09\x66\x74\x4f\x8a\xac\x1b\xc9\x8e\xff\xbe\xfb\xd7\x5d\x43\xde\x41\xd4\x89\x43\x03\x62\xbb\xaf\x80\x55\xf7\x4d\xc0\xb7\x9a\x08\x68\x88\x1f\xd2\x23\x08\x49\x18\x4d\xb7\x37\x9b\x66\x8c\x0b\xc6\x41\x28\x02\x32\xfd\x9c\x34\x9b\x4b\x92\x11\x32\x7c\xd0\xa6\x95\x4a\x10\x5a\xa4\xed\xe7\x97\x76\x86\x24\x49\x25\x88\x23\xc1\xda\x0c\xe3\x56\xdf\xdd\x9f\xe7\xbf\x1f\x61\x37\xf6\xac\xda\x66\xdb\xef\x1c\x29\x05\x82\xfe\x3e\xdd\x5b\x3b\xfc\xe5\x01\xdd\x7e\xff\xdf\xed\x1f\x1f\x6f\xff\x7b\x97\xdd\x6e\x3f\xbc\x37\x86\x1b\xf9\x0a\xd8\x77\xcb\xe7\xb0\x27\x94\x28\xc2\xe8\xb8\x7e\x3a\x22\x5f\xfa\xdf\x5e\xc6\x85\x51\x9e\xb7\x60\x54\x1a\x6b\xef\x51\x29\xc1\xe4\x99\x82\x7a\x62\xe2\x31\xc4\xf3\x08\x7b\x23\x9e\xfb\xf5\x1d\x3c\x9b\xec\x1c\x59\x59\x57\x41\x0d\x0e\xa8\x37\x62\xa6\x5b\x7e\x1d\xfd\x49\xc0\x02\x54\xd8\x64\x3b\xd4\x9b\x59\x6c\xb3\xfc\x75\x0c\x6f\x06\xa6\x67\xb1\x1d\x42\x5b\xbb\xdd\xa0\xe1\xde\x2e\x51\xb9\xdc\xcb\x2f\xab\x51\x58\x1e\x29\xe5\xc0\x4b\x76\x6a\xbe\x79\xe4\xd1\x01\x2a\xa0\x2a\x1d\x45\x90\x24\xe9\xae\x26\x65\x6e\x4b\x94\x51\xf8\xad\x99\xe2\x41\xfb\x98\x24\x3f\xec\x48\xa6\xcd\xd3\x8e\x1b\x7f\xf9\x15\x3e\x8e\x7b\x78\x19\xc7\x31\xa3\x0a\x9e\x55\xcb\xd4\xfc\xd2\x9d\x08\x18\x7e\x04\xb1\x27\x25\xc4\x52\x20\x51\xc8\x19\x91\x95\x44\xaa\x8c\x89\x2c\x27\x58\x39\xe9\x31\xc2\x07\xc8\xf6\x82\x55\xc1\x59\xf6\x59\xb7\x0f\x99\xbe\x58\xf3\x4c\x26\x0e\x1b\xe6\x48\xaa\xfd\xb5\xdd\x38\x26\x4c\x31\xe2\x19\xca\x73\x43\x20\x48\x08\x74\x4a\x6f\x92\x94\x28\xa8\xa4\x5b\x56\x49\x5a\x53\xf2\xad\x86\xff\xf7\x10\x25\x6a\xb0\xe7\xcd\x05\xe3\xeb\x4f\x5c\x08\x56\xf3\x8c\x23\xd1\x58\xea\xbc\x1e\x53\xcc\xaa\x0a\xd1\xb5\xcc\x77\x09\x1f\x11\x92\x67\x54\x21\x42\x41\x64\x14\x55\x21\x8b\x6c\xdc\x17\x68\x2e\xb3\xae\x72\x88\xb5\x24\x63\x82\xb1\x8c\x58\x55\x1f\x39\x9d\xf3\x90\x6e\x9a\xc6\x47\x9a\xbd\xa5\x16\x61\x26\x01\x09\x7c\xb8\x90\x9e\x55\x88\xd0\x18\xd9\x01\x55\xe2\xc4\x19\xe9\xec\xe5\x97\x33\x04\xa0\xc7\x6c\x0c\x4a\x8b\xc5\x00\xf4\x48\x04\xa3\xd5\xe0\x0d\x71\x91\x4a\xa3\x7f\xe6\x4c\x82\x2d\x18\x8b\x41\x7d\x68\x64\xd5\x90\xc9\x40\xf1\x30\x30\x7e\x93\xa4\xb4\xae\x76\x20\x9a\x62\xd8\x40\xee\x99\xa8\x50\xb3\xd9\x61\x6d\x6d\xd8\x90\xb4\xc3\xf2\x74\x01\xea\x3c\x34\xf5\x01\x2a\xb3\x92\xd0\xc7\xf5\x4d\x1c\x9e\x95\x40\xd9\x81\x49\x75\x49\x32\x48\x0f\x80\x4a\x75\xc0\x07\xc0\x8f\x33\xe4\x3a\xca\xa0\x66\x52\xc5\x18\x39\xa9\x50\x11\x06\x71\x1c\x82\x94\x68\x07\xe5\x45\x7c\xae\x2a\x7c\x6d\x5a\x56\x14\x0d\xd4\x67\x71\x93\x12\xa8\x1f\x0e\x15\x0f\xb9\x20\x47\x10\xb1\x95\x00\xe3\xe7\xca\xcd\x1e\x0c\x57\x32\x49\xb8\x8c\x35\xa0\x5f\xee\xba\x2a\x76\xc6\xab\xda\xdf\xca\x32\xdd\xda\xe5\x42\x62\xe5\x7d\xd7\x17\x8b\xc3\xb8\x82\xc2\xd0\x4a\x85\x70\x53\x37\x08\x90\x1e\xbd\x9e\xa1\x7d\x9b\x94\x55\x2c\xf7\x19\xe8\x04\x6c\xcb\xc6\x1b\xa9\x17\x27\xc2\xe4\xa2\x42\x34\x4a\x75\xc1\x4e\x24\xc0\x8d\x6f\x7b\xb1\xdb\x3c\x6f\x37\x6c\x62\x2d\x0e\x95\x04\x49\x08\x3b\xbb\x57\x90\xc6\x6c\x84\x1f\x3f\x45\xda\x84\x8b\xf6\x3f\xb3\xb4\x1e\x52\xef\x9c\xf1\x35\x72\x60\xaa\xf3\x56\x5a\x77\x73\x6d\x64\x1b\xf0\xb6\x57\x2e\xe1\x39\xc9\xfd\xb1\xa2\x8d\x10\xba\x83\x71\x26\xd4\xc4\xbb\x96\xa7\x7b\x9f\x05\xeb\xe2\x1a\xe2\xd4\x39\xe1\x77\x8b\x4f\xa4\x31\x51\x77\x14\xd1\xd4\xff\x82\xfe\x11\xf6\x8c\x74\x26\x4a\x39\xd0\x0a\x89\x02\xcc\x36\x84\x50\x05\x05\x08\x0f\x01\xaf\x77\x25\x91\x07\xc8\x97\xd0\x08\xa6\x18\x66\x65\x9c\x63\x38\xfb\xd8\x78\x67\x30\x27\xdc\x5e\x5d\x9b\x71\x41\x8e\xa4\x84\xc2\xe2\x78\xc7\x58\x09\x88\x1a\x89\x42\x00\xca\x33\x46\xcb\x53\x04\x52\x2a\x24\x82\xed\x9f\x04\x5c\x0b\xa2\x4e\x19\xe3\x6a\xf5\xaa\x50\x1e\xaa\x4c\x92\xef\x60\xfa\xde\xd9\xea\xfb\x89\xb6\xd6\x86\xac\x83\xb1\xe4\xb5\xdc\xcf\x67\xb6\xaf\xe4\x36\x92\xd5\x02\x5f\xe7\x38\xb3\xf8\xda\x0c\x72\xf3\xe0\x62\x09\x78\xe2\xf0\xbd\x0a\x43\x35\xd4\xac\xab\x38\x03\xb5\x3c\x49\xac\x2e\xab\xad\xa5\xca\x09\xcd\x18\x07\x1a\xf4\x0d\xa9\x18\xcf\x0a\x81\x30\x64\x1c\x04\x61\x4e\x51\x18\x01\x36\xaf\x05\x6a\xd6\x9f\x4e\x23\x49\x41\x91\x3b\xee\x68\x50\x55\xf1\xfd\x85\x87\x00\x4a\x85\x9d\xbd\x2e\x49\x45\xfc\x4e\xe3\xb0\xda\x88\x7a\xad\xab\xd5\xdc\x25\xda\x4c\x79\x16\x15\xb2\x67\x3a\x84\xf9\x06\x21\xa2\x33\x38\x20\xb1\x20\x75\xb4\x8e\xb9\xf7\xe4\x27\x57\xdf\xe0\xdc\x97\x71\xc5\xd5\xce\x77\xd3\x6f\x64\xeb\xc4\x2f\x2a\xbd\xec\x6d\x6c\xbd\xd5\x8f\xdb\xa9\x6a\x19\x6c\xe2\x5a\x0c\x95\x73\x0d\xc8\x08\x9d\xde\xd5\x24\x7f\x89\x08\x6d\xe8\xa8\x85\x3b\x74\x13\x11\xc7\xfb\x95\x22\x63\xe7\x6b\x47\xfd\xe8\x8a\x40\xa3\xc1\x8c\x4a\x22\x15\x50\x7c\x8a\x5f\x68\x47\x26\xa7\xc4\x53\xa1\xcc\xf7\x5d\x71\x5d\x57\x8b\x42\x45\x17\x6f\xa3\x1b\x9d\x78\x5f\xed\xaf\xf1\x7e\x0a\x2b\x94\x61\xc6\x3d\xaa\x89\x67\x63\x69\x9a\xb5\x8e\x2e\x66\xea\x50\x5f\xc8\x78\x62\xe2\xb1\x49\x48\x39\x71\x47\x8e\x8d\x45\xb2\xe0\xe6\xd3\x3a\xeb\x1b\x26\x70\x5d\xe9\xe9\xd0\xe0\x15\xe8\xfc\xf5\x62\x0f\xf2\x5e\xfd\x11\x89\x76\xd6\xa5\x97\x2b\xd1\x36\x99\x41\x1c\xc3\xf9\x5e\x80\x12\xc4\xba\x4a\x18\x8a\x26\x3d\xb7\x83\xfc\x35\x0f\xdc\x15\xa9\x80\xd5\xee\x30\xb4\xd1\x0d\xa7\x27\x4a\xb5\xab\xd1\x80\x52\x35\xa4\xad\xd3\x87\x51\xa9\x43\x5f\x1e\x54\x5c\x4c\xc2\x02\x9a\xb7\x57\x1b\x51\xd9\x4d\x00\x2f\x09\x46\x32\x54\x41\x5c\x71\x0a\x5c\xf3\x1c\x29\xc8\xba\x77\x34\x8b\x6a\xb6\x99\x62\x8d\x23\x81\xca\x12\x4a\x22\xab\x98\xe2\x27\xcd\xa1\x44\xce\xe8\x1f\xac\x7b\x5b\xf2\x3d\x22\x65\x2d\x20\x43\xd8\x1b\xa6\x2d\x8a\x8a\x51\xa2\x98\x33\x9c\xc4\x2d\x59\xa1\xe7\x6c\x58\xb6\x85\x84\x5a\x12\xb3\x1b\x8f\x3d\xc0\xd5\x2c\xa1\xcb\xdd\xcb\xca\xea\x19\x15\x9d\x8b\x74\x8f\xc5\x0c\x2b\x4e\x58\x17\x20\x9b\xb0\x33\x9e\xaf\x07\xe9\x57\x95\x82\x42\x42\x65\x9c\x95\xa4\x2b\x17\xd6\x10\x05\x66\xb4\xdb\x47\x8c\xe5\x5c\x69\xaa\x8d\xdd\x34\xcd\x4e\xc5\x55\xd0\xab\x5b\x82\x27\x42\x73\xf6\xb4\x60\xc1\xf5\xa4\xcd\x4b\x84\xc1\x8a\xa2\xd7\x0a\x5a\x2a\x81\x08\x55\x8b\xef\x9d\xae\x65\xeb\x8a\x1a\x61\x34\xe4\x40\x2e\x19\x71\xc1\xea\xc0\x97\x3f\x30\xaf\x83\xb7\x33\x15\x54\x4c\xb8\x2b\xe5\x2b\x78\x1c\x9e\xc6\x05\x58\x1c\x60\x2b\xe4\xca\xa8\xeb\xbc\x1e\x95\x31\xbe\xfe\x79\x42\xf8\xca\x6e\x1b\xee\x66\x09\x47\xd5\x5a\xde\x11\x7d\xc1\x99\x3a\x93\x75\x32\xdf\xf7\x26\xfe\xde\x37\xb4\xeb\xf0\xde\x7b\x84\xac\x77\xd4\xd3\x2e\x4e\xbb\x86\x35\x8f\xbd\x57\x0c\x7a\xc3\xdb\x04\x8f\x56\x1f\xc6\x4a\xfc\x66\x94\xd5\x36\x5a\xc5\xde\x87\x01\xeb\xed\xbf\x6d\x0a\xec\x43\x40\x57\xf7\x80\x94\x42\xf8\x10\xd5\x68\x2c\xac\x2e\xaf\x88\x43\x93\x76\xd8\x19\x86\x7a\xd4\x3f\x51\xe8\x6f\x62\xb3\x3f\xcf\xbe\xfa\x47\xc4\xc1\xd7\xbb\x2d\xea\xe2\x3c\x1e\xf1\x64\xf5\x17\xd0\xd9\x5b\xab\xc2\xbc\x65\xd0\x54\x32\x3d\x74\x98\x93\xe4\xd2\x67\xba\x5b\x73\x1b\x36\xcc\xf1\x7f\x1e\x66\x32\x9d\xbb\x83\x1c\x20\x9e\x43\x2e\x6b\xd1\x5e\x88\xf3\x9c\xaf\x18\x6c\xee\x3e\xcc\x94\x0c\x73\x4f\x96\x5e\x29\xd7\xae\x70\xbf\xeb\xd6\xa9\xd5\x67\x0c\xd2\x9d\xbe\xdd\xf7\xf8\xbf\x46\x3f\x79\xc9\xdf\xf0\x49\x4f\x93\x43\xb1\x1f\xe6\x89\x7e\xf7\x0a\x7f\x6b\xc8\xc7\x82\x74\x0f\x00\xb5\xe8\xbe\xd5\x5b\x2f\x9f\x1a\x9d\xef\xfb\xed\xfb\x84\xe1\x9d\xbd\xe7\x8a\x73\xa3\xff\x6c\xff\x27\x62\xf3\xb2\xf9\x33\x00\x00\xff\xff\x33\x66\x86\x98\x8c\x35\x00\x00") func dataConfig_schema_v32JsonBytes() ([]byte, error) { return bindataRead( @@ -132,7 +132,7 @@ func dataConfig_schema_v32Json() (*asset, error) { return a, nil } -var _dataConfig_schema_v33Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5b\x4b\x93\xdb\x2a\x16\xde\xfb\x57\xa8\x74\xef\x2e\xfd\x48\x55\x52\x53\x35\xd9\xcd\x72\x56\x33\xeb\xe9\x72\x54\x18\x1d\xdb\xa4\x11\x10\x40\xee\x76\x52\xfd\xdf\xa7\xf4\x34\x20\x10\xd8\x56\xa7\x7b\xe6\x66\xd5\x6d\xe9\x3b\xc0\x79\x9f\x03\xe8\xe7\x2a\xcb\xf2\x3f\x15\xde\x43\x85\xf2\x2f\x59\xbe\xd7\x5a\x7c\xb9\xbf\xff\xa6\x38\xbb\xed\x9e\xde\x71\xb9\xbb\x2f\x25\xda\xea\xdb\x8f\x9f\xef\xbb\x67\x7f\xe4\x37\x0d\x1d\x29\x1b\x12\xcc\xd9\x96\xec\x8a\xee\x4d\x71\xf8\x74\xf7\xe9\xae\x21\xef\x20\xfa\x28\xa0\x01\xf1\xcd\x37\xc0\xba\x7b\x26\xe1\x7b\x4d\x24\x34\xc4\x0f\xf9\x01\xa4\x22\x9c\xe5\xeb\x9b\x55\xf3\x4e\x48\x2e\x40\x6a\x02\x2a\xff\x92\x35\x8b\xcb\xb2\x11\x32\x3c\x30\x86\x55\x5a\x12\xb6\xcb\xdb\xc7\x2f\xed\x08\x59\x96\x2b\x90\x07\x82\x8d\x11\xc6\xa5\xfe\x71\x7f\x1a\xff\x7e\x84\xdd\xb8\xa3\x1a\x8b\x6d\x9f\x0b\xa4\x35\x48\xf6\xef\xe9\xda\xda\xd7\x5f\x1f\xd0\xed\x8f\x7f\xdc\xfe\xe7\xe3\xed\xdf\xef\x8a\xdb\xf5\x87\x3f\xad\xd7\x8d\x7c\x25\x6c\xbb\xe9\x4b\xd8\x12\x46\x34\xe1\x6c\x9c\x3f\x1f\x91\x2f\xfd\x7f\x2f\xe3\xc4\xa8\x2c\x5b\x30\xa2\xd6\xdc\x5b\x44\x15\xd8\x3c\x33\xd0\x4f\x5c\x3e\xc6\x78\x1e\x61\x6f\xc4\x73\x3f\xbf\x87\x67\x9b\x9d\x03\xa7\x75\x15\xd5\xe0\x80\x7a\x23\x66\xba\xe9\x97\xd1\x9f\x02\x2c\x41\xc7\x4d\xb6\x43\xbd\x99\xc5\x36\xd3\x2f\xc3\x70\x17\x35\x62\x0c\x0f\xa8\x37\x62\xb8\x9b\xfe\x3a\x86\x57\x03\xd3\xb3\xd8\x0e\x61\xcc\xdd\x2e\xd0\x8a\x67\x3e\x51\xf9\xe2\x49\x58\x56\xa3\xb0\x02\x52\x2a\x41\x50\x7e\x6c\x9e\x05\xe4\xd1\x01\x2a\x60\x3a\x1f\x45\x90\x65\xf9\xa6\x26\xb4\x74\x25\xca\x19\xfc\xab\x19\xe2\xc1\x78\x98\x65\x3f\xdd\xd0\x6d\x8c\xd3\xbe\xb7\x7e\x85\x15\x3e\xbe\x0f\xf0\x32\xbe\xc7\x9c\x69\x78\xd6\x2d\x53\xf3\x53\x77\x22\xe0\xf8\x11\xe4\x96\x50\x48\xa5\x40\xb2\xb3\xe2\x80\xc8\x28\x51\xba\xe0\xb2\x28\x09\xd6\x5e\x7a\x8a\x36\x40\xaf\x1a\x01\x23\xbc\x87\x62\x2b\x79\x15\x1d\x65\x5b\x74\x9c\xa8\xfc\xc5\x19\x67\x32\x70\xdc\xb4\x47\x52\xe3\xd7\x7a\xe5\x19\x30\xc7\x48\x14\xa8\x2c\x2d\x91\x22\x29\xd1\x31\xbf\xc9\x72\xa2\xa1\x52\x7e\x69\x67\x79\xcd\xc8\xf7\x1a\xfe\xd9\x43\xb4\xac\xc1\x1d\xb7\x94\x5c\x2c\x3f\xf0\x4e\xf2\x5a\x14\x02\xc9\xc6\xd6\xe7\x2d\x21\xc7\xbc\xaa\x10\x5b\xca\x01\xce\xe1\x23\x41\xf2\x93\x30\x9b\x99\x5e\xd5\xcf\x61\xbe\x1a\x67\xb3\x96\x15\xe0\x26\xce\xcf\xd4\xa5\xe3\x4e\x1d\x77\xeb\x26\x2a\xf2\x5a\xe2\x54\x2f\x6d\xe6\x44\x72\x07\xa9\x71\x20\xcb\xf2\x9a\x94\xe9\xe0\xdd\x39\xe0\x8a\x97\xf6\xba\x59\x5d\x6d\x40\x4e\x5c\xd2\xf6\xac\xe9\xef\xf5\xca\xf7\xc6\xd1\xbe\x46\x84\x81\x2c\x18\xaa\x62\xb2\xca\xb1\x84\x12\x98\x26\x88\x16\x4a\x00\xb6\xe0\x83\xa6\x66\x34\x93\x27\x45\xcd\x5c\xc2\x8e\x28\x2d\x8f\x5e\xe4\x89\x0b\x73\x61\x25\x08\x60\xa5\x2a\xba\x1e\x20\x35\xc0\x59\x03\x8c\x0d\xc1\xa2\x61\xa2\x64\x73\x81\xbb\x1b\xa6\x09\xdd\xcd\xda\x72\x87\xb0\x50\x80\x24\xde\x5f\x48\xcf\x2b\x44\x58\x8a\x52\x81\x69\x79\x14\x9c\x74\x61\xec\xdd\xc5\x27\x60\x87\x62\xb4\x9b\xb3\xc5\x00\xec\x40\x24\x67\xd5\x10\xa4\xd3\x12\xa8\x41\xff\x2c\xb8\x82\xeb\x83\x63\x4f\xf1\x30\x30\x7e\x33\xfa\xf4\xda\x96\x5e\xbe\xe5\xb2\x42\xcd\x62\x87\xb9\x4d\x1f\xb6\xa6\x9a\x5a\x9e\x29\x40\x93\x87\xa6\xf0\x45\xb4\xa0\x84\x3d\x2e\x6f\xe2\xf0\xac\x25\x2a\xf6\x5c\xe9\x4b\x6a\x94\x7c\x0f\x88\xea\x3d\xde\x03\x7e\x9c\x21\x37\x51\x16\x35\x57\x3a\xc5\xc8\x49\x85\x76\x71\x90\xc0\x31\xc8\xc5\xb5\x58\xbe\xa8\xf0\x8d\x61\xf9\x6e\xd7\x40\x43\x16\x37\xa9\xed\xfb\xd7\xb1\xaa\xb8\x94\xe4\x00\x32\xb5\xc4\xe5\xe2\xd4\x92\xb8\x2f\x53\xb2\x79\xb4\x3f\xb3\xa0\x5f\xef\xba\xf6\x6c\xc6\xab\xda\xff\x28\xcd\xd7\xd3\x94\x39\x4d\x9a\xee\x13\x87\xc3\xb4\x3a\xd7\xd2\x4a\x85\x70\x53\xce\x4a\x50\x01\xbd\x9e\xa0\xfd\x86\x47\x31\xc9\xf9\x27\xec\x04\x3c\x49\xac\xa1\x48\x7d\x76\x22\xcc\x2e\xea\xb0\x92\x54\x17\x6d\xb1\x23\xdc\x84\x96\x97\xba\xcc\xd3\x72\xe3\x26\xd6\xe2\x10\x25\x48\x41\xdc\xd9\x83\x82\xb4\x46\x23\xe2\xf0\x39\xd1\x26\x7c\xb4\x7f\x9b\xa5\x0d\x90\x06\xc7\x4c\x6f\xdd\x22\x43\x99\x25\x2a\xa5\xde\x85\xac\xe3\x45\xeb\x6b\x76\x96\xc2\x2e\xbc\xed\x58\xd1\x46\x08\xd3\xc1\x04\x97\xfa\x97\xf4\x42\xa7\x38\x75\x4a\xf8\xdd\xe4\xd3\xf6\xc8\x55\x77\x12\xd1\xeb\xf4\x54\x33\x51\xca\x83\xf6\x74\x54\x84\x69\xd8\x35\xad\x8c\x3f\x09\xd4\x1b\x4a\xd4\x1e\xca\x73\x68\x24\xd7\x1c\x73\x9a\xe6\x18\xde\x0d\x9a\x74\x67\x98\xe9\xaf\x2e\xaa\xcd\x84\x24\x07\x42\x61\xe7\x70\xbc\xe1\x9c\x02\x62\x56\xa2\x90\x80\xca\x82\x33\x7a\x4c\x40\x2a\x8d\x64\x74\x57\x42\x01\xae\x25\xd1\xc7\x82\x0b\xbd\x78\x55\xa8\xf6\x55\xa1\xc8\x0f\xb0\x7d\xef\x64\xf5\xfd\x40\x6b\x67\x41\xce\x16\x77\xf6\x7b\x2b\xe2\x2f\xb3\x15\xa1\x8e\x0a\xeb\xcb\x6a\x6b\xa5\x4b\xc2\x0a\x2e\x80\x45\x7d\x43\x69\x2e\x8a\x9d\x44\x18\x0a\x01\x92\x70\xaf\x28\xac\x00\x5b\xd6\x12\x35\xf3\x4f\x87\x51\x64\xc7\x90\x3f\xee\x18\x50\x5d\x89\xed\x85\x9b\x00\x5a\xc7\x9d\xbd\xa6\xa4\x22\x61\xa7\xf1\x58\x6d\x42\xbd\xd6\xd5\x6a\xfe\x12\x6d\xa6\x3c\x4b\x0a\xd9\x33\x1d\xc2\x7c\x83\x90\xd0\x19\xec\x91\x3c\x23\x75\xb4\x8e\xb9\x0d\xe4\x27\x5f\xdf\xe0\x5d\x97\x75\x58\xdd\x8e\x77\xd3\x2f\x64\xed\xc5\x9f\x55\x7a\xb9\xcb\x58\x07\xab\x1f\xbf\x53\xd5\x2a\xda\xc4\xb5\x18\xa6\xe6\x1a\x90\x11\x3a\x3d\x75\xcd\xfe\x27\x22\xb4\xa5\xa3\x16\xee\xd1\x4d\x42\x1c\xef\x67\x4a\x8c\x9d\xaf\x1d\xf5\x93\x2b\x02\x83\x06\x73\xa6\x88\xd2\xc0\xb0\x7f\x7f\xd5\x4b\xb4\x21\x93\xc3\x8b\xa9\x50\xe6\xfb\xae\xb4\xae\xab\x45\xa1\x5d\x17\x6f\x93\x1b\x9d\x74\x5f\xed\x0f\xe4\x7f\x09\x2b\x8c\x63\x2e\x02\xaa\x49\x67\xe3\xdc\x34\xeb\x6c\x5d\xcc\xd4\xa1\xa1\x90\xf1\xc4\xe5\x63\x93\x90\x4a\xe2\x8f\x1c\x2b\x87\xe4\x8c\x23\x7d\x67\xaf\x6f\x18\xc0\x77\x56\x6d\x42\xa3\x67\xfb\xf3\xe7\xe6\x3d\x28\x78\xa6\x4d\x14\xda\x38\xe7\x12\xbe\x44\xdb\x64\x06\x79\x88\xe7\x7b\x09\x5a\x12\xe7\x28\x61\x28\x9a\xcc\xdc\x0e\xea\x7d\x6e\xb8\x6b\x52\x01\xaf\xfd\x61\x68\x65\x1a\x4e\x4f\x94\x1b\x67\xfe\x11\xa5\x1a\x48\x57\xa7\x0f\xc6\x01\x52\xd7\x97\x47\x15\x97\x92\xb0\x80\x95\xed\xd1\x46\x52\x76\x93\x20\x28\xc1\x48\xc5\x2a\x88\x2b\x76\x81\x6b\x51\x22\x0d\x45\x7f\x6d\xe4\x9c\x9a\x6d\xa6\x58\x13\x48\x22\x4a\x81\x12\x55\xa5\x14\x3f\x79\x09\x14\x79\xa3\x7f\xb4\xee\x6d\xc9\xb7\x88\xd0\x5a\x42\x81\x70\x30\x4c\x3b\x14\x15\x67\x44\x73\x6f\x38\x49\x9b\xb2\x42\xcf\xc5\x30\x6d\x0b\x89\xb5\x24\x76\x37\x9e\xba\x81\x6b\x58\x42\x97\xbb\xcf\x2b\xab\x67\x54\x74\x2a\xd2\x03\x16\x33\xcc\x38\x61\x5d\x82\x6a\xc2\xce\xb8\xbf\x1e\xa5\x8f\x06\xf8\x7e\x7b\xa0\x10\x9c\x92\xae\x0a\x58\x82\x43\xcc\x59\x27\xe4\x14\x83\xb8\xd2\x02\x1b\x73\x68\x7a\x98\x4a\xe8\xa8\xb3\xb6\x04\x4f\x84\x95\xfc\xe9\x8c\x09\x97\x33\x25\x41\x11\x06\x27\x38\x5e\x2b\x68\xa5\x25\x22\x4c\x9f\x7d\x9c\xe4\x8a\x45\x48\xd8\x82\x04\x36\x35\xf4\x6c\xbe\xac\xcf\xc2\xa5\x7d\x8c\xb7\x38\x87\x3d\x42\x89\xa6\xbe\x7d\x83\x5d\xbd\x6b\x95\x7f\x45\x81\x34\x7a\x71\x24\x91\x8e\xb8\x68\x69\x14\x4a\x9e\x58\xd4\xd1\xa3\xa9\x0a\x2a\x3e\x7f\x0d\xe3\x8a\x8b\xc8\x31\x16\x07\xd8\x02\x85\x42\xd2\x59\x66\x8f\x2a\xb8\x58\x7e\x33\x25\x7e\x5e\xb9\x8e\x87\x6d\x22\x50\xb5\x54\x0c\x49\x3e\xdd\xcd\xbd\x95\x4a\xf6\x0e\xa2\x43\xbd\x61\x81\x5e\xf9\x7d\x47\x07\xfb\xe6\x44\x7b\x31\x23\xa0\xd5\x87\xb1\x0d\xb9\x19\x65\xb5\x4e\x56\x71\xf0\x56\xc4\x72\xeb\x6f\x3b\x22\x77\x07\xd4\xd7\x3a\x21\xad\x11\xde\x27\x75\x59\x67\x96\xd6\x57\xc4\xa1\xc9\x5e\x80\x37\x0c\xf5\xa8\xdf\x51\xe8\xff\xc4\x66\x7f\x9d\x7d\xf5\xdf\x42\x44\xef\xe4\xb7\xa8\x8b\xf3\x78\xc2\x95\xca\x77\xa0\xb3\x37\x56\xc5\x24\x89\x79\x55\xd1\xa3\x7e\xab\xe2\x55\xbd\xc2\x3e\xed\x32\x54\x32\xdd\xfc\x9a\x93\xe4\xb9\x5f\x31\xac\xed\x65\xb8\x30\xcf\x97\x83\x76\x5d\x33\x77\x16\x3e\x40\x02\x9b\xad\xce\xa4\xbd\x10\xe7\x39\x5f\x30\xee\xdf\x7d\x98\xa9\xde\xe6\xae\xce\xbd\x52\xd9\xb3\xc0\x3d\x03\xbf\x4e\x9d\xc6\x78\x90\xee\xf4\xe3\xa8\xb0\xff\x0f\xf4\x93\x4f\xa5\x1a\x3e\xd9\x71\xb2\x39\xfb\xd3\x3e\x59\xea\x3e\x73\x5a\x5b\xf2\x71\x20\xdd\x45\x54\x23\xd1\xae\xcd\xbd\x82\xe0\xcd\x79\xdf\x07\x54\xee\xb9\xd6\xf0\x21\x53\xe0\xa8\x7d\x65\xfe\x6d\x3f\x3a\x5b\xbd\xac\xfe\x1b\x00\x00\xff\xff\x88\x06\xbb\x83\xde\x3b\x00\x00") +var _dataConfig_schema_v33Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5b\xcb\x93\xdb\x2c\x12\xbf\xfb\xaf\x50\xe9\xfb\x6e\x99\x47\xaa\x92\xda\xaa\xcd\x6d\x8f\x7b\xda\x3d\xef\x94\xa3\xc2\xa8\x6d\x93\x41\x40\x00\x79\xc6\x49\xcd\xff\xbe\xa5\xa7\x01\x81\xc0\xb6\x26\x33\xbb\x5f\x4e\x33\x96\x7e\x0d\x74\x37\xfd\xa2\xd1\xcf\x55\x96\xe5\x7f\x2a\xbc\x87\x0a\xe5\x5f\xb2\x7c\xaf\xb5\xf8\x72\x7f\xff\x4d\x71\x76\xdb\x3d\xbd\xe3\x72\x77\x5f\x4a\xb4\xd5\xb7\x1f\x3f\xdf\x77\xcf\xfe\xc8\x6f\x1a\x3a\x52\x36\x24\x98\xb3\x2d\xd9\x15\xdd\x9b\xe2\xf0\xe9\xee\xd3\x5d\x43\xde\x41\xf4\x51\x40\x03\xe2\x9b\x6f\x80\x75\xf7\x4c\xc2\xf7\x9a\x48\x68\x88\x1f\xf2\x03\x48\x45\x38\xcb\xd7\x37\xab\xe6\x9d\x90\x5c\x80\xd4\x04\x54\xfe\x25\x6b\x16\x97\x65\x23\x64\x78\x60\x0c\xab\xb4\x24\x6c\x97\xb7\x8f\x5f\xda\x11\xb2\x2c\x57\x20\x0f\x04\x1b\x23\x8c\x4b\xfd\xe3\xfe\x34\xfe\xfd\x08\xbb\x71\x47\x35\x16\xdb\x3e\x17\x48\x6b\x90\xec\xdf\xd3\xb5\xb5\xaf\xbf\x3e\xa0\xdb\x1f\xff\xb8\xfd\xcf\xc7\xdb\xbf\xdf\x15\xb7\xeb\x0f\x7f\x5a\xaf\x1b\xf9\x4a\xd8\x76\xd3\x97\xb0\x25\x8c\x68\xc2\xd9\x38\x7f\x3e\x22\x5f\xfa\xff\x5e\xc6\x89\x51\x59\xb6\x60\x44\xad\xb9\xb7\x88\x2a\xb0\x79\x66\xa0\x9f\xb8\x7c\x8c\xf1\x3c\xc2\xde\x88\xe7\x7e\x7e\x0f\xcf\x36\x3b\x07\x4e\xeb\x2a\xaa\xc1\x01\xf5\x46\xcc\x74\xd3\x2f\xa3\x3f\x05\x58\x82\x8e\x6f\xd9\x0e\xf5\x66\x3b\xb6\x99\x7e\x19\x86\x3b\xaf\x11\x63\x78\x40\xbd\x11\xc3\xdd\xf4\xd7\x31\xbc\x1a\x98\x9e\xc5\x76\x08\x63\xee\x76\x81\x96\x3f\xf3\x89\xca\xe7\x4f\xc2\xb2\x1a\x85\x15\x90\x52\x09\x82\xf2\x63\xf3\x2c\x20\x8f\x0e\x50\x01\xd3\xf9\x28\x82\x2c\xcb\x37\x35\xa1\xa5\x2b\x51\xce\xe0\x5f\xcd\x10\x0f\xc6\xc3\x2c\xfb\xe9\xba\x6e\x63\x9c\xf6\xbd\xf5\x2b\xac\xf0\xf1\x7d\x80\x97\xf1\x3d\xe6\x4c\xc3\xb3\x6e\x99\x9a\x9f\xba\x13\x01\xc7\x8f\x20\xb7\x84\x42\x2a\x05\x92\xdd\x2e\x0e\x88\x8c\x12\xa5\x0b\x2e\x8b\x92\x60\xed\xa5\xa7\x68\x03\xf4\xaa\x11\x30\xc2\x7b\x28\xb6\x92\x57\xd1\x51\xb6\x45\xc7\x89\xca\x5f\x9c\x71\x26\x03\xc7\xb7\xf6\x48\x6a\xfc\x5a\xaf\x3c\x03\xe6\x18\x89\x02\x95\xa5\x25\x52\x24\x25\x3a\xe6\x37\x59\x4e\x34\x54\xca\x2f\xed\x2c\xaf\x19\xf9\x5e\xc3\x3f\x7b\x88\x96\x35\xb8\xe3\x96\x92\x8b\xe5\x07\xde\x49\x5e\x8b\x42\x20\xd9\xec\xf5\xf9\x9d\x90\x63\x5e\x55\x88\x2d\x65\x00\xe7\xf0\x91\x20\xf9\x89\x9b\xcd\x4c\xab\xea\xe7\x30\x5f\x8d\xb3\x59\xcb\x0a\x70\x13\xe7\x67\x6a\xd2\x71\xa3\x8e\x9b\x75\xe3\x15\x79\x2d\x71\xaa\x95\x36\x73\x22\xb9\x83\x54\x3f\x90\x65\x79\x4d\xca\x74\xf0\xee\x1c\x70\xc5\x4b\x7b\xdd\xac\xae\x36\x20\x27\x26\x69\x5b\xd6\xf4\xf7\x7a\xe5\x7b\xe3\x68\x5f\x23\xc2\x40\x16\x0c\x55\x31\x59\xe5\x58\x42\x09\x4c\x13\x44\x0b\x25\x00\x5b\xf0\x41\x53\x33\x9a\xc9\x93\xbc\x66\x2e\x61\x47\x94\x96\x47\x2f\xf2\xc4\x85\xb9\xb0\x12\x04\xb0\x52\x15\x5d\x0d\x90\xea\xe0\xac\x01\xc6\x82\x60\x51\x37\x51\xb2\x39\xc7\xdd\x0d\xd3\xb8\xee\x66\x6d\xb9\x43\x58\x28\x40\x12\xef\x2f\xa4\xe7\x15\x22\x2c\x45\xa9\xc0\xb4\x3c\x0a\x4e\x3a\x37\xf6\xee\xfc\x13\xb0\x43\x31\xee\x9b\xb3\xc5\x00\xec\x40\x24\x67\xd5\xe0\xa4\xd3\x02\xa8\x41\xff\x2c\xb8\x82\xeb\x9d\x63\x4f\xf1\x30\x30\x7e\x33\xda\xf4\xda\x96\x5e\xbe\xe5\xb2\x42\xcd\x62\x87\xb9\x4d\x1b\xb6\xa6\x9a\xee\x3c\x53\x80\x26\x0f\x4d\xe2\x8b\x68\x41\x09\x7b\x5c\x7e\x8b\xc3\xb3\x96\xa8\xd8\x73\xa5\x2f\xc9\x51\xf2\x3d\x20\xaa\xf7\x78\x0f\xf8\x71\x86\xdc\x44\x59\xd4\x5c\xe9\x94\x4d\x4e\x2a\xb4\x8b\x83\x04\x8e\x41\x2e\xce\xc5\xf2\x45\x85\x6f\x0c\xcb\x77\xbb\x06\x1a\xda\x71\x93\xdc\xbe\x7f\x1d\xcb\x8a\x4b\x49\x0e\x20\x53\x53\x5c\x2e\x4e\x25\x89\xfb\x32\x25\x9a\x47\xeb\x33\x0b\xfa\xf5\xae\x2b\xcf\x66\xac\xaa\xfd\x8f\xd2\x7c\x3d\x0d\x99\xd3\xa0\xe9\x3e\x71\x38\x4c\xcb\x73\x2d\xad\x54\x08\x37\xe9\xac\x04\x15\xd0\xeb\x09\xda\x1f\x78\x14\x93\x98\x7f\xc2\x4e\xc0\x93\xc0\x1a\xf2\xd4\x67\x07\xc2\xec\xa2\x0a\x2b\x49\x75\xd1\x12\x3b\xc2\x4d\x68\x79\xa9\xcb\x3c\x2d\x37\xbe\xc5\x5a\x1c\xa2\x04\x29\x88\x1b\x7b\x50\x90\xd6\x68\x44\x1c\x3e\x27\xee\x09\x1f\xed\xdf\x66\x69\x03\xa4\xc1\x31\xd3\x4b\xb7\xc8\x50\x66\x8a\x4a\xa9\x77\x21\xeb\x78\xd2\xfa\x9a\x95\xa5\xb0\x13\x6f\xdb\x57\xb4\x1e\xc2\x34\x30\xc1\xa5\xfe\x25\xb5\xd0\xc9\x4f\x9d\x02\x7e\x37\xf9\xb4\x3c\x72\xd5\x9d\x44\xf4\x3a\x35\xd5\x8c\x97\xf2\xa0\x3d\x15\x15\x61\x1a\x76\x4d\x29\xe3\x0f\x02\xf5\x86\x12\xb5\x87\xf2\x1c\x1a\xc9\x35\xc7\x9c\xa6\x19\x86\xf7\x80\x26\xdd\x18\x66\xea\xab\x8b\x72\x33\x21\xc9\x81\x50\xd8\x39\x1c\x6f\x38\xa7\x80\x98\x15\x28\x24\xa0\xb2\xe0\x8c\x1e\x13\x90\x4a\x23\x19\x3d\x95\x50\x80\x6b\x49\xf4\xb1\xe0\x42\x2f\x9e\x15\xaa\x7d\x55\x28\xf2\x03\x6c\xdb\x3b\xed\xfa\x7e\xa0\xb5\xb3\x20\xe7\x88\x3b\xfb\x7d\x14\xf1\x97\x39\x8a\x50\x47\x85\xf5\x65\xb9\xb5\xd2\x25\x61\x05\x17\xc0\xa2\xb6\xa1\x34\x17\xc5\x4e\x22\x0c\x85\x00\x49\xb8\x57\x14\x96\x83\x2d\x6b\x89\x9a\xf9\xa7\xc3\x28\xb2\x63\xc8\xef\x77\x0c\xa8\xae\xc4\xf6\xc2\x43\x00\xad\xe3\xc6\x5e\x53\x52\x91\xb0\xd1\x78\x76\x6d\x42\xbe\xd6\xe5\x6a\xfe\x14\x6d\x26\x3d\x4b\x72\xd9\x33\x15\xc2\x7c\x81\x90\x50\x19\xec\x91\x3c\x23\x74\xb4\x86\xb9\x0d\xc4\x27\x5f\xdd\xe0\x5d\x97\xd5\xac\x6e\xc7\xbb\xe9\x17\xb2\xf6\xe2\xcf\x4a\xbd\xdc\x65\xac\x83\xd9\x8f\xdf\xa8\x6a\x15\x2d\xe2\x5a\x0c\x53\x73\x05\xc8\x08\x9d\x76\x5d\xb3\xff\x09\x0f\x6d\xe9\xa8\x85\x7b\x74\x93\xe0\xc7\xfb\x99\x12\x7d\xe7\x6b\x7b\xfd\xe4\x8c\xc0\xa0\xc1\x9c\x29\xa2\x34\x30\xec\x3f\x5f\xf5\x12\x6d\xc8\xa4\x79\x31\x15\xca\x7c\xdd\x95\x56\x75\xb5\x28\xb4\xeb\xfc\x6d\x72\xa1\x93\x6e\xab\x7d\x43\xfe\x97\xb0\xc2\x38\xe6\x22\xa0\x9a\x74\x36\xce\x0d\xb3\xce\xd1\xc5\x4c\x1e\x1a\x72\x19\x4f\x5c\x3e\x36\x01\xa9\x24\x7e\xcf\xb1\x72\x48\xce\x68\xe9\x3b\x67\x7d\xc3\x00\xbe\x5e\xb5\x09\x8d\xf6\xf6\xe7\xfb\xe6\x3d\x28\xd8\xd3\x26\x0a\x6d\x9c\xbe\x84\x2f\xd0\x36\x91\x41\x1e\xe2\xf1\x5e\x82\x96\xc4\x69\x25\x0c\x49\x93\x19\xdb\x41\xbd\xcf\x03\x77\x4d\x2a\xe0\xb5\xdf\x0d\xad\xcc\x8d\xd3\x13\xe5\x46\xcf\x3f\xa2\x54\x03\xe9\xea\xf4\xc1\x68\x20\x75\x75\x79\x54\x71\x29\x01\x0b\x58\xd9\xb6\x36\x92\xa2\x9b\x04\x41\x09\x46\x2a\x96\x41\x5c\x71\x0a\x5c\x8b\x12\x69\x28\xfa\x6b\x23\xe7\xe4\x6c\x33\xc9\x9a\x40\x12\x51\x0a\x94\xa8\x2a\x25\xf9\xc9\x4b\xa0\xc8\xeb\xfd\xa3\x79\x6f\x4b\xbe\x45\x84\xd6\x12\x0a\x84\x83\x6e\xda\xa1\xa8\x38\x23\x9a\x7b\xdd\x49\xda\x94\x15\x7a\x2e\x86\x69\x5b\x48\xac\x24\xb1\xab\xf1\xd4\x03\x5c\x63\x27\x74\xb1\xfb\xbc\xb4\x7a\x46\x45\xa7\x24\x3d\xb0\x63\x86\x19\x27\xac\x4b\x50\x8d\xdb\x19\xcf\xd7\xa3\xf4\x8b\x4a\x41\x23\xa9\x0b\xc1\x29\xe9\xd2\x85\x25\x44\x81\x39\xeb\xd6\x91\xb2\x73\xae\xdc\xaa\xcd\xbe\x69\x8a\x9d\x4a\xe8\xa8\x55\xb7\x04\x4f\x84\x95\xfc\xe9\x8c\x09\x97\x93\xb6\xa0\x08\x83\xe3\x45\xaf\x15\xb4\xd2\x12\x11\xa6\xcf\xee\x3b\xb9\x62\x11\x12\xb6\x20\x81\x4d\x2d\x22\x9b\xcf\xff\xb3\x70\x0d\x10\xe3\x2d\xce\x61\x8f\x50\xa2\x49\x84\xdf\xe0\xf8\xef\x5a\xe5\x5f\x91\x49\x8d\xe6\x1e\x89\xb8\x23\x2e\x9a\x43\x85\xa2\x2c\x16\x75\xb4\x87\x55\x41\xc5\xe7\xef\x6b\x5c\x71\x63\x39\xc6\xe2\x00\x5b\x20\xa3\x48\x6a\x7a\xf6\xa8\x82\x8b\xe5\x4f\x5d\xe2\x8d\xcd\x75\xbc\xe6\x27\x02\x55\x4b\xf9\x90\xe4\x36\x70\xee\x4d\x69\xb2\x77\xe0\x1d\xea\x0d\x0b\x14\xd5\xef\xdb\x3b\xd8\x57\x2c\xda\x1b\x1c\x01\xad\x3e\x8c\xf5\xca\xcd\x28\xab\x75\xb2\x8a\x83\xd7\x27\x96\x5b\x7f\x5b\x3a\xb9\x47\xa5\xbe\x1a\x0b\x69\x8d\xf0\x3e\xa9\x1c\x3b\x33\x07\xbf\xc2\x0f\x4d\x0e\x0d\xbc\x6e\xa8\x47\xfd\xf6\x42\xff\x27\x7b\xf6\xd7\xed\xaf\xfe\xa3\x89\xe8\xe5\xfd\x16\x75\x71\x1c\x4f\xb8\x7b\xf9\x0e\x74\xf6\xc6\xaa\x98\x04\x31\xaf\x2a\x7a\xd4\x6f\x55\xbc\xaa\x55\xd8\x6d\x31\x43\x25\xd3\x53\xb2\x39\x49\x9e\xfb\xb9\xc3\xda\x5e\x86\x0b\xf3\x7c\x62\x68\xe7\x35\x73\x4d\xf3\x01\x12\x38\x95\x75\x26\xed\x85\x38\xcf\xf9\x82\x7e\xff\xee\xc3\x4c\xf6\x36\x77\xc7\xee\x95\xd2\x9e\x05\x2e\x24\xf8\x75\xea\x14\xc6\x83\x74\xa7\x5f\x51\x85\xed\x7f\xa0\x9f\x7c\x53\xd5\xf0\xc9\x8e\x93\x53\xdc\x9f\x76\x0b\xaa\xfb\x1e\x6a\x6d\xc9\xc7\x81\x74\x37\x56\x8d\x40\xbb\x36\xcf\x0a\x82\x57\xec\x7d\x5f\x5a\xb9\x0d\xb0\xe1\x8b\xa7\x40\x4f\x7e\x65\xfe\x6d\xbf\x4e\x5b\xbd\xac\xfe\x1b\x00\x00\xff\xff\x45\x8a\x1d\x5b\x07\x3c\x00\x00") func dataConfig_schema_v33JsonBytes() ([]byte, error) { return bindataRead( @@ -152,7 +152,7 @@ func dataConfig_schema_v33Json() (*asset, error) { return a, nil } -var _dataConfig_schema_v34Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5b\x4f\x73\xe3\x28\x16\xbf\xfb\x53\xa8\x98\xb9\x8d\x93\x4c\xd5\x76\x6d\xd5\xf6\x6d\x8f\x7b\xda\x3d\x6f\xca\xa3\xc2\xe8\xd9\x66\x82\x80\x06\xe4\xc4\xdd\x95\xef\xbe\xa5\xbf\x06\x09\x04\xb6\x95\x4e\x7a\xa7\x4f\x89\xa5\xdf\x03\xde\xff\xf7\x00\x7d\x5b\x65\x19\xfa\x55\x93\x03\x94\x18\x7d\xce\xd0\xc1\x18\xf9\xf9\xe1\xe1\x4f\x2d\xf8\x5d\xfb\xf4\x5e\xa8\xfd\x43\xa1\xf0\xce\xdc\xfd\xfe\xe9\xa1\x7d\xf6\x0b\x5a\xd7\x74\xb4\xa8\x49\x88\xe0\x3b\xba\xcf\xdb\x37\xf9\xf1\x6f\xf7\x9f\xee\x6b\xf2\x16\x62\x4e\x12\x6a\x90\xd8\xfe\x09\xc4\xb4\xcf\x14\x7c\xa9\xa8\x82\x9a\xf8\x11\x1d\x41\x69\x2a\x38\xda\xac\x57\xf5\x3b\xa9\x84\x04\x65\x28\x68\xf4\x39\xab\x17\x97\x65\x03\xa4\x7f\x60\x0d\xab\x8d\xa2\x7c\x8f\x9a\xc7\xaf\xcd\x08\x59\x86\x34\xa8\x23\x25\xd6\x08\xc3\x52\x7f\x79\x38\x8f\xff\x30\xc0\xd6\xe3\x51\xad\xc5\x36\xcf\x25\x36\x06\x14\xff\xcf\x74\x6d\xcd\xeb\x3f\x1e\xf1\xdd\xd7\x7f\xde\xfd\xf7\xf7\xbb\x7f\xdc\xe7\x77\x9b\xdf\x7e\x75\x5e\xd7\xf2\x55\xb0\x6b\xa7\x2f\x60\x47\x39\x35\x54\xf0\x61\x7e\x34\x20\x5f\xbb\xff\x5e\x87\x89\x71\x51\x34\x60\xcc\x9c\xb9\x77\x98\x69\x70\x79\xe6\x60\x9e\x85\x7a\x8a\xf1\x3c\xc0\xde\x89\xe7\x6e\x7e\x0f\xcf\x2e\x3b\x47\xc1\xaa\x32\xaa\xc1\x1e\xf5\x4e\xcc\xb4\xd3\x2f\xa3\x3f\x0d\x44\x81\x89\x9b\x6c\x8b\x7a\x37\x8b\xad\xa7\x5f\x86\xe1\x36\x6a\xc4\x18\xee\x51\xef\xc4\x70\x3b\xfd\x6d\x0c\xaf\x7a\xa6\x67\xb1\x2d\xc2\x9a\xbb\x59\xa0\x13\xcf\x7c\xa2\xf2\xc5\x93\xb0\xac\x06\x61\x05\xa4\x54\x80\x64\xe2\x54\x3f\x0b\xc8\xa3\x05\x94\xc0\x0d\x1a\x44\x90\x65\x68\x5b\x51\x56\x8c\x25\x2a\x38\xfc\xbb\x1e\xe2\xd1\x7a\x98\x65\xdf\xc6\xa1\xdb\x1a\xa7\x79\xef\xfc\x0a\x2b\x7c\x78\x1f\xe0\x65\x78\x4f\x04\x37\xf0\x62\x1a\xa6\xe6\xa7\x6e\x45\x20\xc8\x13\xa8\x1d\x65\x90\x4a\x81\x55\x6b\xc5\x01\x91\x31\xaa\x4d\x2e\x54\x5e\x50\x62\xbc\xf4\x0c\x6f\x81\xdd\x34\x02\xc1\xe4\x00\xf9\x4e\x89\x32\x3a\xca\x2e\x6f\x39\xd1\xe8\x75\x34\xce\x64\xe0\xb8\x69\x0f\xa4\xd6\xaf\xcd\xca\x33\x20\x22\x58\xe6\xb8\x28\x1c\x91\x62\xa5\xf0\x09\xad\x33\x44\x0d\x94\xda\x2f\xed\x0c\x55\x9c\x7e\xa9\xe0\x5f\x1d\xc4\xa8\x0a\xc6\xe3\x16\x4a\xc8\xe5\x07\xde\x2b\x51\xc9\x5c\x62\x55\xdb\xfa\xbc\x25\x20\x22\xca\x12\xf3\xa5\x1c\xe0\x12\x3e\x12\x24\x3f\x09\xb3\x99\xed\x55\xdd\x1c\xf6\xab\x61\x36\x67\x59\x01\x6e\xe2\xfc\x4c\x5d\x3a\xee\xd4\x71\xb7\xae\xa3\xa2\xa8\x14\x49\xf5\xd2\x7a\x4e\xac\xf6\x90\x1a\x07\xb2\x0c\x55\xb4\x48\x07\xef\x2f\x01\x97\xa2\x70\xd7\xcd\xab\x72\x0b\x6a\xe2\x92\xae\x67\x4d\x7f\x6f\x56\xbe\x37\x23\xed\x1b\x4c\x39\xa8\x9c\xe3\x32\x26\x2b\x44\x14\x14\xc0\x0d\xc5\x2c\xd7\x12\x88\x03\xef\x35\x35\xa3\x19\x94\x14\x35\x91\x82\x3d\xd5\x46\x9d\xbc\xc8\x33\x17\xf6\xc2\x0a\x90\xc0\x0b\x9d\xb7\x3d\x40\x6a\x80\x73\x06\x18\x1a\x82\x45\xc3\x44\xc1\xe7\x02\x77\x3b\x4c\x1d\xba\xeb\xb5\xa1\x11\x61\xae\x01\x2b\x72\xb8\x92\x5e\x94\x98\xf2\x14\xa5\x02\x37\xea\x24\x05\x6d\xc3\xd8\x87\x8b\x4f\xc0\x8f\xf9\x60\x37\x17\x8b\x01\xf8\x91\x2a\xc1\xcb\x3e\x48\xa7\x25\x50\x8b\xfe\x45\x0a\x0d\xb7\x07\xc7\x8e\xe2\xb1\x67\x7c\x3d\xf8\xf4\xc6\x95\x1e\xda\x09\x55\xe2\x7a\xb1\xfd\xdc\xb6\x0f\x3b\x53\x4d\x2d\xcf\x16\xa0\xcd\x43\x5d\xf8\x62\x96\x33\xca\x9f\x96\x37\x71\x78\x31\x0a\xe7\x07\xa1\xcd\x35\x35\x0a\x3a\x00\x66\xe6\x40\x0e\x40\x9e\x66\xc8\x6d\x94\x43\x2d\xb4\x49\x31\x72\x5a\xe2\x7d\x1c\x24\x49\x0c\x72\x75\x2d\x86\x16\x15\xbe\x35\xac\xd8\xef\x6b\x68\xc8\xe2\x26\xb5\x7d\xf7\x3a\x56\x15\x17\x8a\x1e\x41\xa5\x96\xb8\x42\x9e\x5b\x92\xf1\xcb\x94\x6c\x1e\xed\xcf\x1c\xe8\x1f\xf7\x6d\x7b\x36\xe3\x55\xcd\x7f\x8c\xa1\xcd\x34\x65\x4e\x93\xe6\xf8\xc9\x88\xc3\xb4\x3a\xd7\xd1\x4a\x89\x49\x5d\xce\x2a\xd0\x01\xbd\x9e\xa1\xdd\x86\x47\x3e\xc9\xf9\x67\xec\x04\x3c\x49\xac\xa1\x48\x7d\x71\x22\xcc\xae\xea\xb0\x92\x54\x17\x6d\xb1\x23\xdc\x84\x96\x97\xba\xcc\xf3\x72\xe3\x26\xd6\xe0\x30\xa3\x58\x43\xdc\xd9\x83\x82\x74\x46\xa3\xf2\xf8\x29\xd1\x26\x7c\xb4\x7f\x9f\xa5\x0d\x90\x06\xc7\x4c\x6f\xdd\x22\x43\xd9\x25\x2a\x63\xde\x85\x6c\xe2\x45\xeb\x5b\x76\x96\xd2\x2d\xbc\xdd\x58\xd1\x44\x08\xdb\xc1\xa4\x50\xe6\xbb\xf4\x42\xe7\x38\x75\x4e\xf8\xed\xe4\xd3\xf6\x68\xac\xee\x24\xa2\xb7\xe9\xa9\x66\xa2\x94\x07\xed\xe9\xa8\x28\x37\xb0\xaf\x5b\x19\x7f\x12\xa8\xb6\x8c\xea\x03\x14\x97\xd0\x28\x61\x04\x11\x2c\xcd\x31\xbc\x1b\x34\xe9\xce\x30\xd3\x5f\x5d\x55\x9b\x49\x45\x8f\x94\xc1\x7e\xc4\xf1\x56\x08\x06\x98\x3b\x89\x42\x01\x2e\x72\xc1\xd9\x29\x01\xa9\x0d\x56\xd1\x5d\x09\x0d\xa4\x52\xd4\x9c\x72\x21\xcd\xe2\x55\xa1\x3e\x94\xb9\xa6\x5f\xc1\xf5\xbd\xb3\xd5\x77\x03\x6d\x46\x0b\x1a\x6d\x71\x67\x3f\xb7\x22\xfe\x32\x5b\x11\xfa\xa4\x89\xb9\xae\xb6\xd6\xa6\xa0\x3c\x17\x12\x78\xd4\x37\xb4\x11\x32\xdf\x2b\x4c\x20\x97\xa0\xa8\xf0\x8a\xc2\x09\xb0\x45\xa5\x70\x3d\xff\x74\x18\x4d\xf7\x1c\xfb\xe3\x8e\x05\x35\xa5\xdc\x5d\xb9\x09\x60\x4c\xdc\xd9\x2b\x46\x4b\x1a\x76\x1a\x8f\xd5\x26\xd4\x6b\x6d\xad\xe6\x2f\xd1\x66\xca\xb3\xa4\x90\x3d\xd3\x21\xcc\x37\x08\x09\x9d\xc1\x01\xab\x0b\x52\x47\xe3\x98\xbb\x40\x7e\xf2\xf5\x0d\xde\x75\x39\x87\xd5\xcd\x78\xeb\x6e\x21\x1b\x2f\xfe\xa2\xd2\x6b\xbc\x8c\x4d\xb0\xfa\xf1\x3b\x55\xa5\xa3\x4d\x5c\x83\xe1\x7a\xae\x01\x19\xa0\xd3\x53\xd7\xec\x87\x88\xd0\x8e\x8e\x1a\xb8\x47\x37\x09\x71\xbc\x9b\x29\x31\x76\xbe\x75\xd4\x4f\xae\x08\x2c\x1a\x22\xb8\xa6\xda\x00\x27\xfe\xfd\x55\x2f\xd1\x96\x4e\x0e\x2f\xa6\x42\x99\xef\xbb\xd2\xba\xae\x06\x85\xf7\x6d\xbc\x4d\x6e\x74\xd2\x7d\xb5\x3b\x90\xff\x2e\xac\x70\x41\x84\x0c\xa8\x26\x9d\x8d\x4b\xd3\xec\x68\xeb\x62\xa6\x0e\x0d\x85\x8c\x67\xa1\x9e\xea\x84\x54\x50\x7f\xe4\x58\x8d\x48\x2e\x38\xd2\x1f\xed\xf5\xf5\x03\xf8\xce\xaa\x6d\x68\xf4\x6c\x7f\xfe\xdc\xbc\x03\x05\xcf\xb4\xa9\xc6\xdb\xd1\xb9\x84\x2f\xd1\xd6\x99\x41\x1d\xe3\xf9\x5e\x81\x51\x74\x74\x94\xd0\x17\x4d\x76\x6e\x07\xfd\x31\x37\xdc\x0d\x2d\x41\x54\xfe\x30\xb4\xb2\x0d\xa7\x23\x42\xd6\x99\x7f\x44\xa9\x16\x72\xac\xd3\x47\xeb\x00\xa9\xed\xcb\xa3\x8a\x4b\x49\x58\xc0\x8b\xe6\x68\x23\x29\xbb\x29\x90\x8c\x12\xac\x63\x15\xc4\x0d\xbb\xc0\x95\x2c\xb0\x81\xbc\xbb\x36\x72\x49\xcd\x36\x53\xac\x49\xac\x30\x63\xc0\xa8\x2e\x53\x8a\x1f\x54\x00\xc3\xde\xe8\x1f\xad\x7b\x1b\xf2\x1d\xa6\xac\x52\x90\x63\x12\x0c\xd3\x23\x8a\x52\x70\x6a\x84\x37\x9c\xa4\x4d\x59\xe2\x97\xbc\x9f\xb6\x81\x44\xbc\xab\x21\x12\xaa\xf0\x17\x3f\xeb\xda\x2e\xaa\xd2\x53\x7e\xa0\xa6\x71\xbe\xdb\x51\xa5\x4d\xdb\xa5\x0a\xd9\xfd\x72\xc3\xec\x6b\xb0\xf3\x4f\xdd\x2c\xb6\xac\xae\xad\x13\x2e\x2b\xe1\x67\xcc\xe1\xdc\x10\x04\xac\xb3\x9f\x71\x22\x31\x05\xba\x0e\x71\xc3\x5e\x7e\x94\x3e\x9a\x4c\xba\xad\x88\x5c\x0a\x46\xdb\x8a\x63\x09\x0e\x89\xe0\xad\x90\x53\x8c\xef\x46\x6b\xaf\x4d\xaf\xee\x97\x4a\x69\xa2\x81\xa1\x21\x78\xa6\xbc\x10\xcf\x17\x4c\xb8\x9c\x29\x49\x86\x09\x8c\x02\xf1\xad\x82\xd6\x46\x61\xca\xcd\xc5\x47\x57\x63\xb1\x48\x05\x3b\x50\xc0\xa7\x86\x9e\xcd\xb7\x10\x59\xb8\x8d\x88\xf1\x16\xe7\xb0\x43\x68\x59\xd7\xd2\xef\xb0\x83\x78\xab\xf2\x6f\x28\xc6\x06\x2f\x8e\x24\xed\x01\x17\x2d\xc3\x42\x89\x9a\xc8\x2a\x7a\x0c\x56\x42\x29\xe6\xaf\x7c\xdc\x70\xe9\x39\xc6\x62\x0f\x5b\xa0\x28\x49\x3a\x37\xed\x50\xb9\x90\xcb\x6f\xdc\xc4\xcf\x46\x37\xf1\xb0\x4d\x25\x2e\x97\x8a\x21\xc9\x27\xc9\xc8\x5b\x15\x65\x1f\x20\x3a\x54\x5b\x1e\xe8\xcb\x3f\x76\x74\x70\x6f\x69\x34\x97\x40\x02\x5a\x7d\x1c\x5a\x9e\xf5\x20\xab\x4d\xb2\x8a\x83\x37\x30\x96\x5b\x7f\xd3\x7d\x8d\x77\x5b\x7d\x6d\x1a\x36\x06\x93\x43\x52\x47\x77\x61\x19\x7f\x43\x1c\x9a\xec\x3b\x78\xc3\x50\x87\x5a\x20\x0a\xa5\x5c\x89\xf9\xff\x88\x54\x3f\xba\x5d\x7f\x3f\x1b\xec\xbe\xcd\x88\x7e\x23\xd0\xa0\xae\xce\xf5\x09\x57\x3c\x3f\x80\xce\xde\x59\x15\x93\x44\xe7\x55\x45\x87\xfa\xa9\x8a\x37\xf5\x0a\xf7\xf4\xcd\x52\xc9\x74\x33\x6e\x4e\x92\x97\x7e\x55\xb1\x71\x97\x31\x86\x79\xbe\x64\x74\x6b\x9f\xb9\xb3\xf9\x1e\x12\xd8\xfc\x1d\x4d\xda\x09\x71\x9e\xf3\x05\xe3\xfe\xfd\x6f\x33\x15\xde\xdc\x55\xbe\x37\x2a\x8d\x16\xb8\xf7\xe0\xd7\xe9\xa8\x79\xee\xa5\x3b\xfd\x58\x2b\xec\xff\x3d\xfd\xe4\xd3\xad\x9a\x4f\x7e\x9a\x6c\x16\x7f\x73\x4f\xba\xda\xcf\xae\x36\x8e\x7c\x46\x90\xf6\x62\xac\x95\x68\x37\xf6\x7e\x42\xf0\x26\xbf\xef\x83\xae\xf1\x39\x5b\xff\x61\x55\xe0\xe8\x7f\x65\xff\x6d\x3e\x82\x5b\xbd\xae\xfe\x17\x00\x00\xff\xff\xf8\x93\x01\x77\x6e\x3c\x00\x00") +var _dataConfig_schema_v34Json = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x5b\x4f\x73\xdb\x3a\x0e\xbf\xfb\x53\x68\xf4\xde\xad\x76\xd2\x99\xed\xec\xcc\xf6\xb6\xc7\x3d\xed\x9e\x37\xe3\x6a\x68\x0a\xb6\xd9\x50\x24\x0b\x52\x4e\xdc\x4e\xbe\xfb\x8e\xfe\x9a\x92\x28\x91\xb2\x95\x26\xdd\xd7\x53\x62\x09\x00\x09\x10\xf8\x01\x20\xa9\x1f\xab\x28\x8a\xff\xd4\xf4\x08\x19\x89\x3f\x47\xf1\xd1\x18\xf5\xf9\xfe\xfe\xab\x96\x62\x53\x3d\xbd\x93\x78\xb8\x4f\x91\xec\xcd\xe6\xe3\xa7\xfb\xea\xd9\x1f\xf1\xba\xe0\x63\x69\xc1\x42\xa5\xd8\xb3\x43\x52\xbd\x49\x4e\x7f\xbb\xfb\x74\x57\xb0\x57\x24\xe6\xac\xa0\x20\x92\xbb\xaf\x40\x4d\xf5\x0c\xe1\x5b\xce\x10\x0a\xe6\x87\xf8\x04\xa8\x99\x14\xf1\x76\xbd\x2a\xde\x29\x94\x0a\xd0\x30\xd0\xf1\xe7\xa8\x98\x5c\x14\xb5\x24\xcd\x03\x4b\xac\x36\xc8\xc4\x21\x2e\x1f\xbf\x94\x12\xa2\x28\xd6\x80\x27\x46\x2d\x09\xed\x54\xff\xb8\xbf\xc8\xbf\x6f\xc9\xd6\x7d\xa9\xd6\x64\xcb\xe7\x8a\x18\x03\x28\xfe\x33\x9c\x5b\xf9\xfa\xcb\x03\xd9\x7c\xff\xe7\xe6\xbf\x1f\x37\xff\xb8\x4b\x36\xdb\x0f\x7f\x76\x5e\x17\xf6\x45\xd8\x57\xc3\xa7\xb0\x67\x82\x19\x26\x45\x3b\x7e\xdc\x52\xbe\xd4\xff\xbd\xb4\x03\x93\x34\x2d\x89\x09\xef\x8c\xbd\x27\x5c\x43\x57\x67\x01\xe6\x49\xe2\xa3\x4f\xe7\x96\xec\x8d\x74\xae\xc7\x77\xe8\xdc\x55\xe7\x24\x79\x9e\x79\x57\xb0\xa1\x7a\x23\x65\xaa\xe1\x97\x59\x3f\x0d\x14\xc1\xf8\x5d\xb6\xa2\x7a\x33\x8f\x2d\x86\x5f\x46\xe1\x0a\x35\x7c\x0a\x37\x54\x6f\xa4\x70\x35\xfc\x6d\x0a\xaf\x1a\xa5\xdd\x73\x8c\xbf\x3c\x6f\x8a\xbf\x2f\xa5\xcc\x49\x79\x95\x14\x6b\x7e\xa5\x12\x1d\xcc\x73\x99\xd3\x85\x39\xe3\xf6\x6c\x0d\x3a\x62\xc9\x14\x14\x97\xe7\x72\xe6\x6e\x9b\x55\x04\x19\x08\x13\xb7\x66\x8a\xa2\x78\x97\x33\x9e\xf6\xad\x2e\x05\xfc\xbb\x10\xf1\x60\x3d\x8c\xa2\x1f\x7d\x78\xb7\xe4\x94\xef\x3b\xbf\xc6\x9d\xa2\x7d\x3f\xa2\x4b\xfb\x9e\x4a\x61\xe0\xd9\x94\x4a\x4d\x0f\x5d\x99\x40\xd2\x47\xc0\x3d\xe3\x10\xca\x41\xb0\xf2\xf4\x11\x93\x71\xa6\x4d\x22\x31\x49\x19\x35\x4e\x7e\x4e\x76\xc0\x6f\x92\x40\x09\x3d\x42\xb2\x47\x99\x79\xa5\xec\x93\x4a\x13\xed\x14\xd4\x20\x78\xa0\xe6\x86\xe0\x01\xdc\x96\xed\x11\x0f\xb8\xfd\xb1\xd5\xb2\x5a\xbf\xb6\x2b\x87\xc0\x98\x12\x95\x90\x34\xed\xcc\x83\x20\x92\x73\xbc\x8e\x62\x66\x20\xd3\x6e\x85\xa2\x38\x17\xec\x5b\x0e\xff\xaa\x49\x0c\xe6\xd0\x97\x9b\xa2\x54\xcb\x0b\x3e\xa0\xcc\x55\xa2\x08\x16\x81\x34\x6d\xec\x98\xca\x2c\x23\x62\xa9\xe8\x9a\xa3\x47\x80\xe5\x07\x38\x1f\xd9\x21\x5b\x8f\x61\xbf\x6a\x47\xeb\x4c\x6b\x44\x1b\xbf\x3e\x43\xbc\xf0\x23\x86\x1f\x33\x0a\xc8\x95\x39\xd2\x50\x08\x98\x0e\x05\x27\x7d\xce\xd2\x70\xe2\xc3\x1c\xe2\x4c\xa6\xdd\x79\x8b\x3c\xdb\x01\x0e\x42\xb2\x1b\x59\xc3\xdf\xdb\x95\xeb\x4d\x6f\xf5\x0d\x61\x02\x30\x11\x24\xf3\xd9\x2a\xa6\x08\x29\x08\xc3\x08\x4f\xb4\x02\xda\x21\x6f\x56\x6a\x62\x65\xe2\x20\x48\x8e\x11\x0e\x4c\x1b\x3c\x4f\x83\xd2\x8b\x3d\xb1\x14\x14\x88\x54\x27\x55\x13\x32\x1f\x3d\xe3\x14\xda\x8e\x64\x51\x98\x48\xc5\x54\x56\xa8\xc4\x14\x79\xa1\x98\x5b\xdc\x63\x4c\x34\x10\xa4\xc7\x2b\xf9\x65\x46\x98\x08\x59\x54\x10\x06\xcf\x4a\xb2\x0a\xc6\xde\x1d\x3e\x81\x38\x25\xad\xdf\xcc\x36\x03\x88\x13\x43\x29\xb2\x06\xa4\xc3\xb2\xb3\xc5\xff\xac\xa4\x86\xdb\xc1\xb1\xe6\x78\x68\x14\x5f\xb7\x31\xbd\xed\x5a\x2f\xde\x4b\xcc\x48\x31\xd9\x66\x6c\x3b\x86\x3b\x43\x0d\x3d\xcf\x36\xa0\xad\x43\x51\xd5\x12\x9e\x70\x26\x1e\x97\x77\x71\x78\x36\x48\x92\xa3\xd4\xe6\x9a\x02\x28\x3e\x02\xe1\xe6\x48\x8f\x40\x1f\x27\xd8\x6d\xaa\x0e\xb7\xd4\x26\xc4\xc9\x59\x46\x0e\x7e\x22\x45\x7d\x24\x57\x17\x7a\xf1\xa2\xc6\xb7\xc4\xca\xc3\xa1\x20\x1d\xf3\xb8\x41\xe3\x50\xbf\xf6\x95\xdc\x29\xb2\x13\x60\x68\x15\x29\xd5\xa5\xdf\xe9\xbf\x0c\xc9\xe6\xde\x06\xb1\x43\xfa\xe5\xae\xea\x0f\x27\xa2\xaa\xfc\x8f\xf3\x78\x3b\x4c\x99\xc3\xa4\xd9\x7f\xd2\xd3\x30\xac\xce\xed\xac\x4a\x46\x68\x51\xce\x22\xe8\x91\x75\xbd\x90\xd6\xf5\x7a\x32\xc8\xf9\x17\xda\x01\xf1\x20\xb1\x8e\x21\xf5\x55\x6d\xc4\xfc\xf6\x2d\x68\xe9\xbc\x3d\xbe\x47\x9b\xb1\xe9\x85\x4e\xf3\x32\x5d\xbf\x8b\x95\x74\x84\x33\xa2\xc1\x1f\xec\x93\xfd\x58\x2b\x8d\xa9\xd3\xa7\x40\x9f\x70\xf1\xfe\x7d\x92\x77\x84\x75\x54\x66\x78\xeb\xe6\x11\x65\x97\xa8\x9c\x3b\x27\xb2\xf5\x17\xad\xaf\xd9\x59\xaa\x6e\xe1\xdd\xc5\x8a\x12\x21\xec\x00\x53\x12\xcd\x4f\xe9\x85\x2e\x38\x75\x49\xf8\xd5\xe0\xc3\xf6\xa8\xbf\xdc\x41\x4c\xaf\xd3\x53\x4d\xa0\x94\x83\xda\xd1\x51\x31\x61\xe0\x50\xb4\x32\xee\x24\x90\xef\x38\xd3\x47\x48\xe7\xf0\xa0\x34\x92\x4a\x1e\x16\x18\xce\xdd\x9f\xf0\x60\x98\xe8\xaf\xae\xaa\xcd\x14\xb2\x13\xe3\x70\xe8\x69\xbc\x93\x92\x03\x11\x9d\x44\x81\x40\xd2\x44\x0a\x7e\x0e\xa0\xd4\x86\xa0\x77\x57\x42\x03\xcd\x91\x99\x73\x22\x95\x59\xbc\x2a\xd4\xc7\x2c\xd1\xec\x3b\x74\x63\xef\xe2\xf5\xb5\xa0\x6d\x6f\x42\xbd\x3d\xf6\xe8\xf7\x56\xc4\x5f\x66\x2b\x42\x9f\x35\x35\xd7\xd5\xd6\xda\xa4\x4c\x24\x52\x81\xf0\xc6\x86\x36\x52\x25\x07\x24\x14\x12\x05\xc8\xa4\xd3\x14\x1d\x80\x4d\x73\x24\xc5\xf8\x43\x31\x9a\x1d\x04\x71\xe3\x8e\x45\x6a\x32\xb5\xbf\x72\x13\xc0\x18\x7f\xb0\xe7\x9c\x65\x6c\x3c\x68\x1c\x5e\x1b\x50\xaf\x55\xb5\x9a\xbb\x44\x9b\x28\xcf\x82\x20\x7b\xa2\x43\x98\x6e\x10\x02\x3a\x83\x23\xc1\x19\xa9\xa3\x0c\xcc\xfd\x48\x7e\x72\xf5\x0d\xce\x79\x75\x4e\xcb\x4b\x79\xeb\x7a\x22\x5b\x27\xfd\xac\xd2\xab\x3f\x8d\xed\x68\xf5\xe3\x0e\xaa\x5c\x7b\x9b\xb8\x92\x46\xe8\xa9\x06\xa4\x25\x1d\x1e\xfb\x46\xbf\x04\x42\x77\xd6\xa8\x24\x77\xac\x4d\x00\x8e\xd7\x23\x05\x62\xe7\x6b\xa3\x7e\x70\x45\x60\xf1\x50\x29\x34\xd3\x06\x04\x75\xef\xaf\x3a\x99\x76\x6c\x70\x78\x31\x34\xca\x74\xdf\x15\xd6\x75\x95\x54\xe4\x50\xe1\x6d\x70\xa3\x13\x1e\xab\xf5\x8d\x80\x9f\xa2\x8a\x90\x54\xaa\x91\xa5\x09\x57\x63\x6e\x9a\xed\x6d\x5d\x4c\xd4\xa1\x63\x90\xf1\x24\xf1\xb1\x48\x48\x29\x73\x23\xc7\xaa\xc7\x32\xe3\x4e\x41\x6f\xaf\xaf\x11\xe0\x3a\x08\xb7\x49\xbd\x97\x0b\xa6\x0f\xe5\x6b\xa2\xd1\x03\x73\xa6\xc9\xae\x77\x2e\xe1\x4a\xb4\x45\x66\xc0\x93\x3b\xdf\xfb\x0b\x06\x04\x83\xac\x77\xc0\xd0\x94\x52\x76\xc6\x07\xfd\x3e\xb7\xe1\x0d\xcb\x40\xe6\x4e\x70\x0a\xa9\x96\x08\x9a\xf9\xf5\xd6\xca\x76\xd3\x5a\x5e\x6c\x5d\x5f\xf0\xb8\x90\x45\xd9\xf7\xa0\x07\xeb\xb8\xaa\xda\x05\xf0\xba\x49\x48\x7a\x04\x91\x96\x07\x29\x41\xb9\x14\x41\x71\x46\x89\xf6\xd5\x2b\x37\xec\x39\xe7\x2a\x25\x06\x92\xfa\x96\xcc\x9c\x0a\x71\xa2\x34\x54\x04\x09\xe7\xc0\x99\xce\x42\x4a\xad\x38\x05\x4e\x9c\xb9\xc6\xeb\x37\x25\xfb\x9e\x30\x9e\x23\x24\x84\x8e\x26\x85\x1e\x47\x26\x05\x33\xd2\x09\x5e\x61\x43\x66\xe4\x39\x69\x86\x2d\x49\x3c\x51\x5b\x32\x49\x4c\xdd\xa5\xd6\xba\xf0\x8b\x3c\x73\x14\x3b\x55\x5c\x6c\xf6\x0c\xb5\xa9\x7a\x62\xa9\xea\x5f\x5d\x50\x7f\x19\xdd\x67\x08\xdd\x9a\xb6\xbc\xae\xaa\x4a\xe6\x35\x0c\x13\xee\x70\x69\x3f\x46\xbc\xb3\x19\x71\x60\x31\x04\x5d\x00\x6a\x7b\x72\xe0\xe5\x5f\xd4\x0a\x15\x24\x49\xce\xaa\x42\x68\x09\x53\x50\x29\xaa\x79\x84\x78\xe9\x8d\x61\x51\xf8\x68\xd1\xc6\x65\xca\x78\x11\xa4\x64\x78\x62\x22\x95\x4f\xf3\xd1\x77\x01\x6b\x2b\x4e\x28\xf4\x10\xfb\x56\x43\x6b\x83\x84\x09\x33\xfb\x44\xad\x6f\x16\x85\xb0\x07\x04\x31\x8c\x88\x68\xba\xb3\x89\xc6\xbb\x1b\x9f\x6e\x7e\x0d\x6b\x0a\xad\x8a\x12\xff\x0d\x36\x36\x6f\x5d\xfc\x1b\x6a\xc4\x36\xdc\x3d\xd9\xbd\xa5\xf3\x56\x87\x63\x19\x9d\xaa\xdc\x7b\x3a\x97\x41\x26\xa7\x6f\xa2\xdc\x70\x19\xdc\xa7\x62\x43\xb6\x40\xf5\x12\x74\x9c\x5b\x53\x25\x52\x2d\xbf\x9f\xe4\x3f\xb2\xdd\xfa\x77\x33\x98\x22\xd9\x52\x18\x12\x7c\xc0\x1d\x3b\xcb\xa7\xe8\x1d\xa0\x43\xbe\x13\x61\x57\x37\xdf\x19\x3a\x74\x2f\x8f\x94\x77\x53\x46\x56\xf5\xa1\xed\xc4\xd6\xad\xad\xb6\xc1\x4b\x3c\x7a\x31\x64\xb9\xf9\x97\x4d\x61\x7f\x13\xd8\xd5\x3d\x12\x63\x08\x3d\x06\x35\x9a\x33\xeb\xfd\x1b\x70\x68\xb0\x1d\xe2\x84\xa1\x9a\x6a\x01\x14\x0a\xb9\xa9\xf3\xff\x81\x54\xbf\xba\x5f\xff\x3c\x1f\xac\xbf\x59\xf1\x7e\x17\x51\x52\x5d\x9d\xeb\x03\x6e\x9e\xbe\x83\x35\x7b\xe3\xa5\x18\x24\x3a\xe7\x52\xd4\x54\xbf\x97\xe2\x55\xa3\xa2\x7b\x28\x68\x2d\xc9\x70\x37\x70\xca\x92\xc1\x37\x97\x6a\x8e\x6d\x77\x1a\x7d\x32\xc7\x17\x9e\xdd\xda\x67\xea\xca\x40\x43\x32\xb2\x27\xdd\x1b\xb4\x36\xe2\xb4\xe6\x0b\xe2\xfe\xdd\x87\x89\x0a\x6f\xea\x86\xe1\x2b\x95\x46\x0b\x5c\xc7\x70\xaf\x69\xaf\x79\x6e\xac\x3b\xfc\x40\x6d\x3c\xfe\x1b\xfe\xc1\xe7\x6a\x85\x9e\xe2\x3c\xd8\xad\xfe\xd1\x3d\x80\xab\x3e\x35\xdb\x76\xec\xd3\x23\xa9\xee\xeb\x5a\x89\x76\x6b\xef\x27\x8c\x7e\x60\xe0\xfa\x88\xad\x7f\xfc\xd7\x7c\x4c\x36\x72\x23\x61\x65\xff\x2d\x3f\x0e\x5c\xbd\xac\xfe\x17\x00\x00\xff\xff\xf4\xc7\x3d\x6c\x86\x3d\x00\x00") func dataConfig_schema_v34JsonBytes() ([]byte, error) { return bindataRead( diff --git a/components/cli/cli/compose/schema/data/config_schema_v3.0.json b/components/cli/cli/compose/schema/data/config_schema_v3.0.json index fbcd8bb859..f39344cfbe 100644 --- a/components/cli/cli/compose/schema/data/config_schema_v3.0.json +++ b/components/cli/cli/compose/schema/data/config_schema_v3.0.json @@ -240,7 +240,8 @@ "properties": { "limits": {"$ref": "#/definitions/resource"}, "reservations": {"$ref": "#/definitions/resource"} - } + }, + "additionalProperties": false }, "restart_policy": { "type": "object", diff --git a/components/cli/cli/compose/schema/data/config_schema_v3.1.json b/components/cli/cli/compose/schema/data/config_schema_v3.1.json index b7037485f9..719c0fa7ac 100644 --- a/components/cli/cli/compose/schema/data/config_schema_v3.1.json +++ b/components/cli/cli/compose/schema/data/config_schema_v3.1.json @@ -269,7 +269,8 @@ "properties": { "limits": {"$ref": "#/definitions/resource"}, "reservations": {"$ref": "#/definitions/resource"} - } + }, + "additionalProperties": false }, "restart_policy": { "type": "object", diff --git a/components/cli/cli/compose/schema/data/config_schema_v3.2.json b/components/cli/cli/compose/schema/data/config_schema_v3.2.json index a5bd3686fa..8d850d5d2e 100644 --- a/components/cli/cli/compose/schema/data/config_schema_v3.2.json +++ b/components/cli/cli/compose/schema/data/config_schema_v3.2.json @@ -314,7 +314,8 @@ "properties": { "limits": {"$ref": "#/definitions/resource"}, "reservations": {"$ref": "#/definitions/resource"} - } + }, + "additionalProperties": false }, "restart_policy": { "type": "object", diff --git a/components/cli/cli/compose/schema/data/config_schema_v3.3.json b/components/cli/cli/compose/schema/data/config_schema_v3.3.json index e69116c388..f1eb9a6610 100644 --- a/components/cli/cli/compose/schema/data/config_schema_v3.3.json +++ b/components/cli/cli/compose/schema/data/config_schema_v3.3.json @@ -348,7 +348,8 @@ "properties": { "limits": {"$ref": "#/definitions/resource"}, "reservations": {"$ref": "#/definitions/resource"} - } + }, + "additionalProperties": false }, "restart_policy": { "type": "object", diff --git a/components/cli/cli/compose/schema/data/config_schema_v3.4.json b/components/cli/cli/compose/schema/data/config_schema_v3.4.json index ce9512076b..f2e315ba8e 100644 --- a/components/cli/cli/compose/schema/data/config_schema_v3.4.json +++ b/components/cli/cli/compose/schema/data/config_schema_v3.4.json @@ -64,6 +64,7 @@ } }, + "patternProperties": {"^x-": {}}, "additionalProperties": false, "definitions": { @@ -84,7 +85,9 @@ "dockerfile": {"type": "string"}, "args": {"$ref": "#/definitions/list_or_dict"}, "labels": {"$ref": "#/definitions/list_or_dict"}, - "cache_from": {"$ref": "#/definitions/list_of_strings"} + "cache_from": {"$ref": "#/definitions/list_of_strings"}, + "network": {"type": "string"}, + "target": {"type": "string"} }, "additionalProperties": false } @@ -313,7 +316,7 @@ "additionalProperties": false, "properties": { "disable": {"type": "boolean"}, - "interval": {"type": "string"}, + "interval": {"type": "string", "format": "duration"}, "retries": {"type": "number"}, "test": { "oneOf": [ @@ -321,7 +324,8 @@ {"type": "array", "items": {"type": "string"}} ] }, - "timeout": {"type": "string"} + "timeout": {"type": "string", "format": "duration"}, + "start_period": {"type": "string", "format": "duration"} } }, "deployment": { @@ -351,7 +355,8 @@ "properties": { "limits": {"$ref": "#/definitions/resource"}, "reservations": {"$ref": "#/definitions/resource"} - } + }, + "additionalProperties": false }, "restart_policy": { "type": "object", diff --git a/components/cli/cli/compose/schema/schema_test.go b/components/cli/cli/compose/schema/schema_test.go index f293fe7f68..6a761fddef 100644 --- a/components/cli/cli/compose/schema/schema_test.go +++ b/components/cli/cli/compose/schema/schema_test.go @@ -36,6 +36,16 @@ func TestValidateUndefinedTopLevelOption(t *testing.T) { assert.Contains(t, err.Error(), "Additional property helicopters is not allowed") } +func TestValidateAllowsXTopLevelFields(t *testing.T) { + config := dict{ + "version": "3.4", + "x-extra-stuff": dict{}, + } + + err := Validate(config, "3.4") + assert.NoError(t, err) +} + func TestValidateInvalidVersion(t *testing.T) { config := dict{ "version": "2.1", diff --git a/components/cli/cli/compose/types/types.go b/components/cli/cli/compose/types/types.go index 8feaf4289d..5b230f5100 100644 --- a/components/cli/cli/compose/types/types.go +++ b/components/cli/cli/compose/types/types.go @@ -23,6 +23,7 @@ var UnsupportedProperties = []string{ "shm_size", "sysctls", "tmpfs", + "ulimits", "userns_mode", } @@ -169,10 +170,10 @@ type DeployConfig struct { // HealthCheckConfig the healthcheck configuration for a service type HealthCheckConfig struct { Test HealthCheckTest - Timeout string - Interval string + Timeout *time.Duration + Interval *time.Duration Retries *uint64 - StartPeriod string + StartPeriod *time.Duration `mapstructure:"start_period"` Disable bool } diff --git a/components/cli/cli/config/config_test.go b/components/cli/cli/config/config_test.go index c885d3e724..25ed97cdf1 100644 --- a/components/cli/cli/config/config_test.go +++ b/components/cli/cli/config/config_test.go @@ -10,8 +10,8 @@ import ( "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/credentials" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/pkg/homedir" - "github.com/docker/docker/pkg/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/components/cli/cli/config/credentials/native_store_test.go b/components/cli/cli/config/credentials/native_store_test.go index 582c2bd598..c85e3f0d18 100644 --- a/components/cli/cli/config/credentials/native_store_test.go +++ b/components/cli/cli/config/credentials/native_store_test.go @@ -8,10 +8,10 @@ import ( "strings" "testing" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker-credential-helpers/client" "github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/testutil" "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" diff --git a/components/cli/contrib/completion/bash/docker b/components/cli/contrib/completion/bash/docker index 542b0013ba..f9186d819a 100644 --- a/components/cli/contrib/completion/bash/docker +++ b/components/cli/contrib/completion/bash/docker @@ -1862,7 +1862,7 @@ _docker_container_run_and_create() { __docker_complete_containers_running ;; *) - COMPREPLY=( $( compgen -W 'host container:' -- "$cur" ) ) + COMPREPLY=( $( compgen -W 'none host private shareable container:' -- "$cur" ) ) # shellcheck disable=SC2128 if [ "$COMPREPLY" = "container:" ]; then __docker_nospace @@ -3101,6 +3101,7 @@ _docker_service() { logs ls rm + rollback scale ps update @@ -3215,6 +3216,20 @@ _docker_service_rm() { esac } +_docker_service_rollback() { + case "$cur" in + -*) + COMPREPLY=( $( compgen -W "--detach=false -d --help --quit -q" -- "$cur" ) ) + ;; + *) + local counter=$( __docker_pos_first_nonflag ) + if [ "$cword" -eq "$counter" ]; then + __docker_complete_services + fi + ;; + esac +} + _docker_service_scale() { case "$cur" in -*) @@ -4392,7 +4407,7 @@ _docker_stack_ps() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--all -a --filter -f --format --help --no-resolve --no-trunc --quiet -q" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--filter -f --format --help --no-resolve --no-trunc --quiet -q" -- "$cur" ) ) ;; *) local counter=$(__docker_pos_first_nonflag '--filter|-f') diff --git a/components/cli/contrib/completion/zsh/_docker b/components/cli/contrib/completion/zsh/_docker index 6e0c55b4dd..4150a85fe9 100644 --- a/components/cli/contrib/completion/zsh/_docker +++ b/components/cli/contrib/completion/zsh/_docker @@ -1931,6 +1931,7 @@ __docker_service_commands() { "logs:Fetch the logs of a service or task" "ls:List services" "rm:Remove one or more services" + "rollback:Revert changes to a service's configuration" "scale:Scale one or multiple replicated services" "ps:List the tasks of a service" "update:Update a service" @@ -2037,6 +2038,13 @@ __docker_service_subcommand() { $opts_help \ "($help -)*:service:__docker_complete_services" && ret=0 ;; + (rollback) + _arguments $(__docker_arguments) \ + $opts_help \ + "($help -d --detach)"{-d=false,--detach=false}"[Disable detached mode]" \ + "($help -q --quiet)"{-q,--quiet}"[Suppress progress output]" \ + "($help -)*:service:__docker_complete_services" && ret=0 + ;; (scale) _arguments $(__docker_arguments) \ $opts_help \ diff --git a/components/cli/docker.Makefile b/components/cli/docker.Makefile index 9b958397a1..097ad49399 100644 --- a/components/cli/docker.Makefile +++ b/components/cli/docker.Makefile @@ -4,10 +4,10 @@ # Makefile for developing using Docker # -DEV_DOCKER_IMAGE_NAME = docker-cli-dev -LINTER_IMAGE_NAME = docker-cli-lint -CROSS_IMAGE_NAME = docker-cli-cross -VALIDATE_IMAGE_NAME = docker-cli-shell-validate +DEV_DOCKER_IMAGE_NAME = docker-cli-dev$(IMAGE_TAG) +LINTER_IMAGE_NAME = docker-cli-lint$(IMAGE_TAG) +CROSS_IMAGE_NAME = docker-cli-cross$(IMAGE_TAG) +VALIDATE_IMAGE_NAME = docker-cli-shell-validate$(IMAGE_TAG) MOUNTS = -v "$(CURDIR)":/go/src/github.com/docker/cli VERSION = $(shell cat VERSION) ENVVARS = -e VERSION=$(VERSION) -e GITCOMMIT @@ -39,12 +39,12 @@ build: binary # clean build artifacts using a container .PHONY: clean clean: build_docker_image - docker run --rm $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make clean + docker run --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make clean # run go test -.PHONY: test -test: build_docker_image - docker run --rm $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make test +.PHONY: test-unit +test-unit: build_docker_image + docker run --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make test-unit # build the CLI for multiple architectures using a container .PHONY: cross @@ -53,24 +53,26 @@ cross: build_cross_image .PHONY: watch watch: build_docker_image - docker run --rm $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make watch + docker run --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make watch # start container in interactive mode for in-container development .PHONY: dev dev: build_docker_image - docker run -ti $(MOUNTS) -v /var/run/docker.sock:/var/run/docker.sock $(DEV_DOCKER_IMAGE_NAME) ash + docker run -ti $(ENVVARS) $(MOUNTS) \ + -v /var/run/docker.sock:/var/run/docker.sock \ + $(DEV_DOCKER_IMAGE_NAME) ash shell: dev # run linters in a container .PHONY: lint lint: build_linter_image - docker run -ti $(MOUNTS) $(LINTER_IMAGE_NAME) + docker run -ti $(ENVVARS) $(MOUNTS) $(LINTER_IMAGE_NAME) # download dependencies (vendor/) listed in vendor.conf, using a container .PHONY: vendor vendor: build_docker_image vendor.conf - docker run -ti --rm $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make vendor + docker run -ti --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make vendor dynbinary: build_cross_image docker run -ti --rm $(ENVVARS) $(MOUNTS) $(CROSS_IMAGE_NAME) make dynbinary @@ -78,13 +80,17 @@ dynbinary: build_cross_image ## generate man pages from go source and markdown .PHONY: manpages manpages: build_docker_image - docker run -ti --rm $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make manpages + docker run -ti --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make manpages ## Generate documentation YAML files consumed by docs repo .PHONY: yamldocs yamldocs: build_docker_image - docker run -ti --rm $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make yamldocs + docker run -ti --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make yamldocs .PHONY: shellcheck shellcheck: build_shell_validate_image - docker run -ti --rm $(MOUNTS) $(VALIDATE_IMAGE_NAME) make shellcheck + docker run -ti --rm $(ENVVARS) $(MOUNTS) $(VALIDATE_IMAGE_NAME) make shellcheck + +.PHONY: test-e2e +test-e2e: binary + ./scripts/test/e2e/wrapper diff --git a/components/cli/dockerfiles/Dockerfile.cross b/components/cli/dockerfiles/Dockerfile.cross index 60487dca90..d4b900cab1 100644 --- a/components/cli/dockerfiles/Dockerfile.cross +++ b/components/cli/dockerfiles/Dockerfile.cross @@ -1,2 +1,3 @@ FROM dockercore/golang-cross@sha256:d24e7affa3a85d460d2303c2549f03fc866f2b97d771ccf07b0e6e2b411dd207 +ENV DISABLE_WARN_OUTSIDE_CONTAINER=1 WORKDIR /go/src/github.com/docker/cli diff --git a/components/cli/dockerfiles/Dockerfile.dev b/components/cli/dockerfiles/Dockerfile.dev index 5b9d35f516..dc67eeb07f 100644 --- a/components/cli/dockerfiles/Dockerfile.dev +++ b/components/cli/dockerfiles/Dockerfile.dev @@ -25,6 +25,7 @@ RUN go get -d github.com/dnephin/filewatcher && \ rm -rf /go/src/* /go/pkg/* /go/bin/* ENV CGO_ENABLED=0 \ - PATH=$PATH:/go/src/github.com/docker/cli/build + PATH=$PATH:/go/src/github.com/docker/cli/build \ + DISABLE_WARN_OUTSIDE_CONTAINER=1 WORKDIR /go/src/github.com/docker/cli CMD sh diff --git a/components/cli/dockerfiles/Dockerfile.lint b/components/cli/dockerfiles/Dockerfile.lint index c981efbadc..4b3cccf085 100644 --- a/components/cli/dockerfiles/Dockerfile.lint +++ b/components/cli/dockerfiles/Dockerfile.lint @@ -12,5 +12,6 @@ RUN go get -d github.com/alecthomas/gometalinter && \ WORKDIR /go/src/github.com/docker/cli ENV CGO_ENABLED=0 +ENV DISABLE_WARN_OUTSIDE_CONTAINER=1 ENTRYPOINT ["/usr/local/bin/gometalinter"] CMD ["--config=gometalinter.json", "./..."] diff --git a/components/cli/dockerfiles/Dockerfile.shellcheck b/components/cli/dockerfiles/Dockerfile.shellcheck index 12f665f7a7..43112b314d 100644 --- a/components/cli/dockerfiles/Dockerfile.shellcheck +++ b/components/cli/dockerfiles/Dockerfile.shellcheck @@ -1,9 +1,9 @@ -FROM debian:stretch-slim +FROM debian:stretch-slim -RUN apt-get update && \ - apt-get -y install make shellcheck && \ - apt-get clean +RUN apt-get update && \ + apt-get -y install make shellcheck && \ + apt-get clean WORKDIR /go/src/github.com/docker/cli - -CMD bash +ENV DISABLE_WARN_OUTSIDE_CONTAINER=1 +CMD bash diff --git a/components/cli/dockerfiles/Dockerfile.test-e2e-env b/components/cli/dockerfiles/Dockerfile.test-e2e-env new file mode 100644 index 0000000000..3c672f4e8c --- /dev/null +++ b/components/cli/dockerfiles/Dockerfile.test-e2e-env @@ -0,0 +1,17 @@ +FROM docker/compose:1.15.0 + +RUN apk add -U bash curl + +ARG DOCKER_CHANNEL=edge +ARG DOCKER_VERSION=17.06.0-ce +RUN export URL=https://download.docker.com/linux/static; \ + curl -Ls $URL/$DOCKER_CHANNEL/x86_64/docker-$DOCKER_VERSION.tgz | \ + tar -xz docker/docker && \ + mv docker/docker /usr/local/bin/ && \ + rmdir docker +ENV DISABLE_WARN_OUTSIDE_CONTAINER=1 +WORKDIR /work +COPY scripts/test/e2e scripts/test/e2e +COPY e2e/compose-env.yaml e2e/compose-env.yaml + +ENTRYPOINT ["bash", "/work/scripts/test/e2e/run"] diff --git a/components/cli/docs/extend/plugins_logging.md b/components/cli/docs/extend/plugins_logging.md index 43203d3c58..c8df2045b9 100644 --- a/components/cli/docs/extend/plugins_logging.md +++ b/components/cli/docs/extend/plugins_logging.md @@ -213,7 +213,7 @@ to determine what set of logs to read. **Response**: ``` -{{ log stream }} +{% raw %}{{ log stream }}{% endraw %} ``` The response should be the encoded log message using the same format as the diff --git a/components/cli/docs/reference/.builder.md.swp b/components/cli/docs/reference/.builder.md.swp new file mode 100644 index 0000000000..c551264cda Binary files /dev/null and b/components/cli/docs/reference/.builder.md.swp differ diff --git a/components/cli/docs/reference/builder.md b/components/cli/docs/reference/builder.md index cf4f79faea..f7f717d241 100644 --- a/components/cli/docs/reference/builder.md +++ b/components/cli/docs/reference/builder.md @@ -301,9 +301,9 @@ Results in: ---> Running in a2c157f842f5 Volume in drive C has no label. Volume Serial Number is 7E6D-E0F7 - + Directory of c:\ - + 10/05/2016 05:04 PM 1,894 License.txt 10/05/2016 02:22 PM Program Files 10/05/2016 02:14 PM Program Files (x86) @@ -381,7 +381,7 @@ throughout the entire instruction. In other words, in this example: ENV ghi=$abc will result in `def` having a value of `hello`, not `bye`. However, -`ghi` will have a value of `bye` because it is not part of the same instruction +`ghi` will have a value of `bye` because it is not part of the same instruction that set `abc` to `bye`. ## .dockerignore file @@ -415,12 +415,12 @@ temp? This file causes the following build behavior: -| Rule | Behavior | -|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `# comment` | Ignored. | -| `*/temp*` | Exclude files and directories whose names start with `temp` in any immediate subdirectory of the root. For example, the plain file `/somedir/temporary.txt` is excluded, as is the directory `/somedir/temp`. | -| `*/*/temp*` | Exclude files and directories starting with `temp` from any subdirectory that is two levels below the root. For example, `/somedir/subdir/temporary.txt` is excluded. | -| `temp?` | Exclude files and directories in the root directory whose names are a one-character extension of `temp`. For example, `/tempa` and `/tempb` are excluded. +| Rule | Behavior | +|:------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `# comment` | Ignored. | +| `*/temp*` | Exclude files and directories whose names start with `temp` in any immediate subdirectory of the root. For example, the plain file `/somedir/temporary.txt` is excluded, as is the directory `/somedir/temp`. | +| `*/*/temp*` | Exclude files and directories starting with `temp` from any subdirectory that is two levels below the root. For example, `/somedir/subdir/temporary.txt` is excluded. | +| `temp?` | Exclude files and directories in the root directory whose names are a one-character extension of `temp`. For example, `/tempa` and `/tempb` are excluded. | Matching is done using Go's @@ -493,32 +493,32 @@ Or FROM [@] [AS ] -The `FROM` instruction initializes a new build stage and sets the -[*Base Image*](glossary.md#base-image) for subsequent instructions. As such, a +The `FROM` instruction initializes a new build stage and sets the +[*Base Image*](glossary.md#base-image) for subsequent instructions. As such, a valid `Dockerfile` must start with a `FROM` instruction. The image can be -any valid image – it is especially easy to start by **pulling an image** from +any valid image – it is especially easy to start by **pulling an image** from the [*Public Repositories*](https://docs.docker.com/engine/tutorials/dockerrepos/). - `ARG` is the only instruction that may precede `FROM` in the `Dockerfile`. See [Understand how ARG and FROM interact](#understand-how-arg-and-from-interact). -- `FROM` can appear multiple times within a single `Dockerfile` to +- `FROM` can appear multiple times within a single `Dockerfile` to create multiple images or use one build stage as a dependency for another. - Simply make a note of the last image ID output by the commit before each new + Simply make a note of the last image ID output by the commit before each new `FROM` instruction. Each `FROM` instruction clears any state created by previous instructions. -- Optionally a name can be given to a new build stage by adding `AS name` to the +- Optionally a name can be given to a new build stage by adding `AS name` to the `FROM` instruction. The name can be used in subsequent `FROM` and `COPY --from=` instructions to refer to the image built in this stage. -- The `tag` or `digest` values are optional. If you omit either of them, the +- The `tag` or `digest` values are optional. If you omit either of them, the builder assumes a `latest` tag by default. The builder returns an error if it cannot find the `tag` value. ### Understand how ARG and FROM interact -`FROM` instructions support variables that are declared by any `ARG` +`FROM` instructions support variables that are declared by any `ARG` instructions that occur before the first `FROM`. ```Dockerfile @@ -754,20 +754,26 @@ This will then be visible from `docker inspect` with the other labels. ## EXPOSE - EXPOSE [...] + EXPOSE [/...] The `EXPOSE` instruction informs Docker that the container listens on the -specified network ports at runtime. `EXPOSE` does not make the ports of the -container accessible to the host. To do that, you must use either the `-p` flag -to publish a range of ports or the `-P` flag to publish all of the exposed -ports. You can expose one port number and publish it externally under another -number. +specified network ports at runtime. You can specify whether the port listens on +TCP or UDP, and the default is TCP if the protocol is not specified. + +The `EXPOSE` instruction does not actually publish the port. It functions as a +type of documentation between the person who builds the image and the person who +runs the container, about which ports are intended to be published. To actually +publish the port when running the container, use the `-p` flag on `docker run` +to publish and map one or more ports, or the `-P` flag to publish all exposed +ports and map them to to high-order ports. To set up port redirection on the host system, see [using the -P -flag](run.md#expose-incoming-ports). The Docker network feature supports -creating networks without the need to expose ports within the network, for -detailed information see the [overview of this -feature](https://docs.docker.com/engine/userguide/networking/)). +flag](run.md#expose-incoming-ports). The `docker network` command supports +creating networks for communication among containers without the need to +expose or publish specific ports, because the containers connected to the +network can communicate with each other over any port. For detailed information, +see the +[overview of this feature](https://docs.docker.com/engine/userguide/networking/)). ## ENV @@ -976,9 +982,9 @@ All new files and directories are created with a UID and GID of 0. Optionally `COPY` accepts a flag `--from=` that can be used to set the source location to a previous build stage (created with `FROM .. AS `) -that will be used instead of a build context sent by the user. The flag also -accepts a numeric index assigned for all previous build stages started with -`FROM` instruction. In case a build stage with a specified name can't be found an +that will be used instead of a build context sent by the user. The flag also +accepts a numeric index assigned for all previous build stages started with +`FROM` instruction. In case a build stage with a specified name can't be found an image with the same name is attempted to be used instead. `COPY` obeys the following rules: @@ -1250,7 +1256,7 @@ or for executing an ad-hoc command in a container. The table below shows what command is executed for different `ENTRYPOINT` / `CMD` combinations: | | No ENTRYPOINT | ENTRYPOINT exec_entry p1_entry | ENTRYPOINT ["exec_entry", "p1_entry"] | -|--------------------------------|----------------------------|--------------------------------|------------------------------------------------| +|:-------------------------------|:---------------------------|:-------------------------------|:-----------------------------------------------| | **No CMD** | *error, not allowed* | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry | | **CMD ["exec_cmd", "p1_cmd"]** | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd | | **CMD ["p1_cmd", "p2_cmd"]** | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd | @@ -1288,7 +1294,7 @@ Keep the following things in mind about volumes in the `Dockerfile`. - **Volumes on Windows-based containers**: When using Windows-based containers, the destination of a volume inside the container must be one of: - + - a non-existing or empty directory - a drive other than `C:` @@ -1805,16 +1811,16 @@ Resulting in: Removing intermediate container 6fcdb6855ae2 Step 3/5 : RUN New-Item -ItemType Directory C:\Example ---> Running in d0eef8386e97 - - + + Directory: C:\ - - + + Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 10/28/2016 11:26 AM Example - - + + ---> 3f2fbf1395d9 Removing intermediate container d0eef8386e97 Step 4/5 : ADD Execute-MyCmdlet.ps1 c:\example\ diff --git a/components/cli/docs/reference/commandline/build.md b/components/cli/docs/reference/commandline/build.md index 1e535edd2b..5f6623756d 100644 --- a/components/cli/docs/reference/commandline/build.md +++ b/components/cli/docs/reference/commandline/build.md @@ -78,7 +78,7 @@ When the `URL` parameter points to the location of a Git repository, the repository acts as the build context. The system recursively fetches the repository and its submodules. The commit history is not preserved. A repository is first pulled into a temporary directory on your local host. After -the that succeeds, the directory is sent to the Docker daemon as the context. +that succeeds, the directory is sent to the Docker daemon as the context. Local copy gives you the ability to access private repositories using local user credentials, VPN's, and so forth. diff --git a/components/cli/docs/reference/commandline/run.md b/components/cli/docs/reference/commandline/run.md index 985707a31a..37282a2847 100644 --- a/components/cli/docs/reference/commandline/run.md +++ b/components/cli/docs/reference/commandline/run.md @@ -302,7 +302,7 @@ Contents of file ``` The following examples will fail when using Windows-based containers, as the -destination of a volume or bind-mount inside the container must be one of: +destination of a volume or bind mount inside the container must be one of: a non-existing or empty directory; or a drive other than C:. Further, the source of a bind mount must be a local directory, not a file. @@ -318,7 +318,7 @@ docker run -v c:\foo:c:\existing-directory-with-contents ... For in-depth information about volumes, refer to [manage data in containers](https://docs.docker.com/engine/tutorials/dockervolumes/) -### Add bind-mounts or volumes using the --mount flag +### Add bind mounts or volumes using the --mount flag The `--mount` flag allows you to mount volumes, host-directories and `tmpfs` mounts in a container. @@ -753,8 +753,9 @@ is set on the cgroup and applications in a container can query it at `/sys/fs/cg On Windows, this will affect containers differently depending on what type of isolation is used. - With `process` isolation, Windows will report the full memory of the host system, not the limit to applications running inside the container + ```powershell - docker run -it -m 2GB --isolation=process microsoft/nanoserver powershell Get-ComputerInfo *memory* + PS C:\> docker run -it -m 2GB --isolation=process microsoft/nanoserver powershell Get-ComputerInfo *memory* CsTotalPhysicalMemory : 17064509440 CsPhyicallyInstalledMemory : 16777216 @@ -765,9 +766,11 @@ On Windows, this will affect containers differently depending on what type of is OsInUseVirtualMemory : 1957488 OsMaxProcessMemorySize : 137438953344 ``` + - With `hyperv` isolation, Windows will create a utility VM that is big enough to hold the memory limit, plus the minimal OS needed to host the container. That size is reported as "Total Physical Memory." + ```powershell - docker run -it -m 2GB --isolation=hyperv microsoft/nanoserver powershell Get-ComputerInfo *memory* + PS C:\> docker run -it -m 2GB --isolation=hyperv microsoft/nanoserver powershell Get-ComputerInfo *memory* CsTotalPhysicalMemory : 2683355136 CsPhyicallyInstalledMemory : diff --git a/components/cli/docs/reference/commandline/search.md b/components/cli/docs/reference/commandline/search.md index 0482dbb143..c33521e8da 100644 --- a/components/cli/docs/reference/commandline/search.md +++ b/components/cli/docs/reference/commandline/search.md @@ -25,6 +25,7 @@ Options: - is-automated=(true|false) - is-official=(true|false) - stars= - image has at least 'number' stars + --format string Pretty-print images using a Go template --help Print usage --limit int Max number of search results (default 25) --no-trunc Don't truncate output @@ -144,3 +145,58 @@ NAME DESCRIPTION STARS O progrium/busybox 50 [OK] radial/busyboxplus Full-chain, Internet enabled, busybox made... 8 [OK] ``` + +### Format the output + +The formatting option (`--format`) pretty-prints search output +using a Go template. + +Valid placeholders for the Go template are: + +| Placeholder | Description | +| -------------- | --------------------------------- | +| `.Name` | Image Name | +| `.Description` | Image description | +| `.StarCount` | Number of stars for the image | +| `.IsOfficial` | "OK" if image is official | +| `.IsAutomated` | "OK" if image build was automated | + +When you use the `--format` option, the `search` command will +output the data exactly as the template declares. If you use the +`table` directive, column headers are included as well. + +The following example uses a template without headers and outputs the +`Name` and `StarCount` entries separated by a colon for all images: + +```bash +{% raw %} +$ docker search --format "{{.Name}}: {{.StarCount}}" nginx + +nginx: 5441 +jwilder/nginx-proxy: 953 +richarvey/nginx-php-fpm: 353 +million12/nginx-php: 75 +webdevops/php-nginx: 70 +h3nrik/nginx-ldap: 35 +bitnami/nginx: 23 +evild/alpine-nginx: 14 +million12/nginx: 9 +maxexcloo/nginx: 7 +{% endraw %} +``` + +This example outputs a table format: + +```bash +{% raw %} +$ docker search --format "table {{.Name}}\t{{.IsAutomated}}\t{{.IsOfficial}}" nginx + +NAME AUTOMATED OFFICIAL +nginx [OK] +jwilder/nginx-proxy [OK] +richarvey/nginx-php-fpm [OK] +jrcs/letsencrypt-nginx-proxy-companion [OK] +million12/nginx-php [OK] +webdevops/php-nginx [OK] +{% endraw %} +``` diff --git a/components/cli/docs/reference/commandline/service_create.md b/components/cli/docs/reference/commandline/service_create.md index 184e9f952a..f82c030993 100644 --- a/components/cli/docs/reference/commandline/service_create.md +++ b/components/cli/docs/reference/commandline/service_create.md @@ -243,21 +243,19 @@ $ docker service create \ For more information about labels, refer to [apply custom metadata](https://docs.docker.com/engine/userguide/labels-custom-metadata/). -### Add bind-mounts or volumes +### Add bind mounts, volumes or memory filesystems -Docker supports two different kinds of mounts, which allow containers to read to -or write from files or directories on other containers or the host operating -system. These types are _data volumes_ (often referred to simply as volumes) and -_bind-mounts_. +Docker supports three different kinds of mounts, which allow containers to read +from or write to files or directories, either on the host operating system, or +on memory filesystems. These types are _data volumes_ (often referred to simply +as volumes), _bind mounts_, and _tmpfs_. -Additionally, Docker supports `tmpfs` mounts. - -A **bind-mount** makes a file or directory on the host available to the -container it is mounted within. A bind-mount may be either read-only or +A **bind mount** makes a file or directory on the host available to the +container it is mounted within. A bind mount may be either read-only or read-write. For example, a container might share its host's DNS information by -means of a bind-mount of the host's `/etc/resolv.conf` or a container might +means of a bind mount of the host's `/etc/resolv.conf` or a container might write logs to its host's `/var/log/myContainerLogs` directory. If you use -bind-mounts and your host and containers have different notions of permissions, +bind mounts and your host and containers have different notions of permissions, access controls, or other such details, you will run into portability issues. A **named volume** is a mechanism for decoupling persistent data needed by your @@ -281,7 +279,7 @@ update the named volume. For more information about named volumes, see [Data Volumes](https://docs.docker.com/engine/tutorials/dockervolumes/). -The following table describes options which apply to both bind-mounts and named +The following table describes options which apply to both bind mounts and named volumes in a service: @@ -330,7 +328,7 @@ volumes in a service: @@ -364,15 +362,15 @@ volumes in a service: #### Bind Propagation Bind propagation refers to whether or not mounts created within a given -bind-mount or named volume can be propagated to replicas of that mount. Consider +bind mount or named volume can be propagated to replicas of that mount. Consider a mount point `/mnt`, which is also mounted on `/tmp`. The propation settings control whether a mount on `/tmp/a` would also be available on `/mnt/a`. Each propagation setting has a recursive counterpoint. In the case of recursion, consider that `/tmp/a` is also mounted as `/foo`. The propagation settings control whether `/mnt/a` and/or `/tmp/a` would exist. -The `bind-propagation` option defaults to `rprivate` for both bind-mounts and -volume mounts, and is only configurable for bind-mounts. In other words, named +The `bind-propagation` option defaults to `rprivate` for both bind mounts and +volume mounts, and is only configurable for bind mounts. In other words, named volumes do not support bind propagation. - **`shared`**: Sub-mounts of the original mount are exposed to replica mounts, @@ -877,9 +875,10 @@ x3ti0erg11rjpg64m75kej2mz-hosttempl * [service inspect](service_inspect.md) * [service logs](service_logs.md) * [service ls](service_ls.md) -* [service rm](service_rm.md) -* [service scale](service_scale.md) * [service ps](service_ps.md) +* [service rm](service_rm.md) +* [service rollback](service_rollback.md) +* [service scale](service_scale.md) * [service update](service_update.md) diff --git a/components/cli/docs/reference/commandline/service_inspect.md b/components/cli/docs/reference/commandline/service_inspect.md index af09b52be2..cbc954a5ae 100644 --- a/components/cli/docs/reference/commandline/service_inspect.md +++ b/components/cli/docs/reference/commandline/service_inspect.md @@ -164,7 +164,8 @@ $ docker service inspect --format='{{.Spec.Mode.Replicated.Replicas}}' redis * [service create](service_create.md) * [service logs](service_logs.md) * [service ls](service_ls.md) -* [service rm](service_rm.md) -* [service scale](service_scale.md) * [service ps](service_ps.md) +* [service rm](service_rm.md) +* [service rollback](service_rollback.md) +* [service scale](service_scale.md) * [service update](service_update.md) diff --git a/components/cli/docs/reference/commandline/service_logs.md b/components/cli/docs/reference/commandline/service_logs.md index cf12169bcf..4f8f35a62d 100644 --- a/components/cli/docs/reference/commandline/service_logs.md +++ b/components/cli/docs/reference/commandline/service_logs.md @@ -79,7 +79,8 @@ fraction of a second no more than nine digits long. You can combine the * [service create](service_create.md) * [service inspect](service_inspect.md) * [service ls](service_ls.md) -* [service rm](service_rm.md) -* [service scale](service_scale.md) * [service ps](service_ps.md) +* [service rm](service_rm.md) +* [service rollback](service_rollback.md) +* [service scale](service_scale.md) * [service update](service_update.md) diff --git a/components/cli/docs/reference/commandline/service_ls.md b/components/cli/docs/reference/commandline/service_ls.md index 1625dd254d..4ee551be3c 100644 --- a/components/cli/docs/reference/commandline/service_ls.md +++ b/components/cli/docs/reference/commandline/service_ls.md @@ -158,7 +158,8 @@ fm6uf97exkul: global 5/5 * [service create](service_create.md) * [service inspect](service_inspect.md) * [service logs](service_logs.md) -* [service rm](service_rm.md) -* [service scale](service_scale.md) * [service ps](service_ps.md) +* [service rm](service_rm.md) +* [service rollback](service_rollback.md) +* [service scale](service_scale.md) * [service update](service_update.md) diff --git a/components/cli/docs/reference/commandline/service_ps.md b/components/cli/docs/reference/commandline/service_ps.md index 51e3a28afe..88a963ae68 100644 --- a/components/cli/docs/reference/commandline/service_ps.md +++ b/components/cli/docs/reference/commandline/service_ps.md @@ -190,5 +190,6 @@ top.3: busybox * [service logs](service_logs.md) * [service ls](service_ls.md) * [service rm](service_rm.md) +* [service rollback](service_rollback.md) * [service scale](service_scale.md) * [service update](service_update.md) diff --git a/components/cli/docs/reference/commandline/service_rm.md b/components/cli/docs/reference/commandline/service_rm.md index 742fc38c80..1e3ada3d7d 100644 --- a/components/cli/docs/reference/commandline/service_rm.md +++ b/components/cli/docs/reference/commandline/service_rm.md @@ -55,6 +55,7 @@ ID NAME MODE REPLICAS IMAGE * [service inspect](service_inspect.md) * [service logs](service_logs.md) * [service ls](service_ls.md) -* [service scale](service_scale.md) * [service ps](service_ps.md) +* [service rollback](service_rollback.md) +* [service scale](service_scale.md) * [service update](service_update.md) diff --git a/components/cli/docs/reference/commandline/service_rollback.md b/components/cli/docs/reference/commandline/service_rollback.md new file mode 100644 index 0000000000..94ac2607e3 --- /dev/null +++ b/components/cli/docs/reference/commandline/service_rollback.md @@ -0,0 +1,94 @@ +--- +title: "service rollback" +description: "The service rollback command description and usage" +keywords: "service, rollback" +--- + + + +# service rollback + +```markdown +Usage: docker service rollback SERVICE + +Revert changes to a service's configuration + +Options: + -d, --detach Exit immediately instead of waiting for the service to converge (default true) + --help Print usage + -q, --quiet Suppress progress output +``` + +## Description + +Roll back a specified service to its previous version from the swarm. This command must be run +targeting a manager node. + +## Examples + +### Roll back to the previous version of a service + +Use the `docker service rollback` command to roll back to the previous version +of a service. After executing this command, the service is reverted to the +configuration that was in place before the most recent `docker service update` +command. + +The following example creates a service with a single replica, updates the +service to use three replicas, and then rolls back the service to the +previous version, having one replica. + +Create a service with a single replica: + +```bash +$ docker service create --name my-service -p 8080:80 nginx:alpine +``` + +Confirm that the service is running with a single replica: + +```bash +$ docker service ls + +ID NAME MODE REPLICAS IMAGE PORTS +xbw728mf6q0d my-service replicated 1/1 nginx:alpine *:8080->80/tcp +``` + +Update the service to use three replicas: + +```bash +$ docker service update --replicas=3 my-service + +$ docker service ls + +ID NAME MODE REPLICAS IMAGE PORTS +xbw728mf6q0d my-service replicated 3/3 nginx:alpine *:8080->80/tcp +``` + +Now roll back the service to its previous version, and confirm it is +running a single replica again: + +```bash +$ docker service rollback my-service + +$ docker service ls + +ID NAME MODE REPLICAS IMAGE PORTS +xbw728mf6q0d my-service replicated 1/1 nginx:alpine *:8080->80/tcp +``` + +## Related commands + +* [service create](service_create.md) +* [service inspect](service_inspect.md) +* [service logs](service_logs.md) +* [service ls](service_ls.md) +* [service ps](service_ps.md) +* [service rm](service_rm.md) +* [service scale](service_scale.md) +* [service update](service_update.md) diff --git a/components/cli/docs/reference/commandline/service_scale.md b/components/cli/docs/reference/commandline/service_scale.md index d16c850480..17a06a3ba3 100644 --- a/components/cli/docs/reference/commandline/service_scale.md +++ b/components/cli/docs/reference/commandline/service_scale.md @@ -101,5 +101,6 @@ ID NAME MODE REPLICAS IMAGE * [service logs](service_logs.md) * [service ls](service_ls.md) * [service rm](service_rm.md) +* [service rollback](service_rollback.md) * [service ps](service_ps.md) * [service update](service_update.md) diff --git a/components/cli/docs/reference/commandline/service_update.md b/components/cli/docs/reference/commandline/service_update.md index 32cc8abed2..1c6dd973e3 100644 --- a/components/cli/docs/reference/commandline/service_update.md +++ b/components/cli/docs/reference/commandline/service_update.md @@ -136,7 +136,7 @@ that the rolling restart happens gradually. ### Add or remove mounts -Use the `--mount-add` or `--mount-rm` options add or remove a service's bind-mounts +Use the `--mount-add` or `--mount-rm` options add or remove a service's bind mounts or volumes. The following example creates a service which mounts the `test-data` volume to @@ -147,7 +147,7 @@ service name. - The `--mount-add` flag takes the same parameters as the `--mount` flag on `service create`. Refer to the [volumes and - bind-mounts](service_create.md#volumes-and-bind-mounts-mount) section in the + bind mounts](service_create.md#volumes-and-bind-mounts-mount) section in the `service create` reference for details. - The `--mount-rm` flag takes the `target` path of the mount. @@ -174,9 +174,9 @@ $ docker service update --mount-rm /somewhere myservice myservice ``` -### Rolling back to the previous version of a service +### Roll back to the previous version of a service -Use the `--rollback` option to roll back to the previous version of the service. +Use the `--rollback` option to roll back to the previous version of the service. This will revert the service to the configuration that was in place before the most recent `docker service update` command. @@ -193,7 +193,7 @@ ID NAME MODE REPLICAS IMAGE 80bvrzp6vxf3 web replicated 0/5 nginx:alpine ``` -Roll back the `web` service... +Roll back the `web` service... ```bash $ docker service update --rollback web @@ -266,4 +266,5 @@ See [`service create`](./service_create.md#templating) for the reference. * [service ls](service_ls.md) * [service ps](service_ps.md) * [service rm](service_rm.md) +* [service rollback](service_rollback.md) * [service scale](service_scale.md) diff --git a/components/cli/docs/reference/commandline/stack_deploy.md b/components/cli/docs/reference/commandline/stack_deploy.md index dd9a4cd65c..15feaa8519 100644 --- a/components/cli/docs/reference/commandline/stack_deploy.md +++ b/components/cli/docs/reference/commandline/stack_deploy.md @@ -57,6 +57,23 @@ Creating service vossibility_ghollector Creating service vossibility_lookupd ``` +The Compose file can also be provided as standard input with `--compose-file -`: + +```bash +$ cat docker-compose.yml | docker stack deploy --compose-file - vossibility + +Ignoring unsupported options: links + +Creating network vossibility_vossibility +Creating network vossibility_default +Creating service vossibility_nsqd +Creating service vossibility_logstash +Creating service vossibility_elasticsearch +Creating service vossibility_kibana +Creating service vossibility_ghollector +Creating service vossibility_lookupd +``` + Only a single Compose file is accepted. If your configuration is split between multiple Compose files, e.g. a base configuration and environment-specific overrides, you can combine these by passing them to `docker-compose config` with the `-f` option diff --git a/components/cli/docs/reference/commandline/update.md b/components/cli/docs/reference/commandline/update.md index bcd83ddda3..9bb1f51aec 100644 --- a/components/cli/docs/reference/commandline/update.md +++ b/components/cli/docs/reference/commandline/update.md @@ -51,6 +51,10 @@ options on a running or a stopped container. On kernel version older than 4.6, you can only update `--kernel-memory` on a stopped container or on a running container with kernel memory initialized. +> **Warning**: The `docker update` and `docker container update` commands are +> not supported for Windows containers. +{: .warning } + ## Examples The following sections illustrate ways to use this command. diff --git a/components/cli/docs/reference/run.md b/components/cli/docs/reference/run.md index 4a1a4c1cb9..ad53741726 100644 --- a/components/cli/docs/reference/run.md +++ b/components/cli/docs/reference/run.md @@ -265,11 +265,21 @@ more advanced use case would be changing the host's hostname from a container. ## IPC settings (--ipc) - --ipc="" : Set the IPC mode for the container, - 'container:': reuses another container's IPC namespace - 'host': use the host's IPC namespace inside the container + --ipc="MODE" : Set the IPC mode for the container -By default, all containers have the IPC namespace enabled. +The following values are accepted: + +| Value | Description | +|:---------------------------|:----------------------------------------------------------------------------------| +| "" | Use daemon's default. | +| "none" | Own private IPC namespace, with /dev/shm not mounted. | +| "private" | Own private IPC namespace. | +| "shareable" | Own private IPC namespace, with a possibility to share it with other containers. | +| "container:<_name-or-ID_>" | Join another ("shareable") container's IPC namespace. | +| "host" | Use the host system's IPC namespace. | + +If not specified, daemon default is used, which can either be `"private"` +or `"shareable"`, depending on the daemon version and configration. IPC (POSIX/SysV IPC) namespace provides separation of named shared memory segments, semaphores and message queues. @@ -280,7 +290,8 @@ memory is commonly used by databases and custom-built (typically C/OpenMPI, C++/using boost libraries) high performance applications for scientific computing and financial services industries. If these types of applications are broken into multiple containers, you might need to share the IPC mechanisms -of the containers. +of the containers, using `"shareable"` mode for the main (i.e. "donor") +container, and `"container:"` for other containers. ## Network settings @@ -1577,7 +1588,7 @@ followed by `a-z0-9`, `_` (underscore), `.` (period) or `-` (hyphen). An absolute path starts with a `/` (forward slash). For example, you can specify either `/foo` or `foo` for a `host-src` value. -If you supply the `/foo` value, Docker creates a bind-mount. If you supply +If you supply the `/foo` value, Docker creates a bind mount. If you supply the `foo` specification, Docker creates a named volume. ### USER diff --git a/components/cli/e2e/compose-env.yaml b/components/cli/e2e/compose-env.yaml new file mode 100644 index 0000000000..afc95e3af0 --- /dev/null +++ b/components/cli/e2e/compose-env.yaml @@ -0,0 +1,10 @@ +version: '2.1' + +services: + registry: + image: 'registry:2' + + engine: + image: 'docker:${TEST_ENGINE_VERSION:-edge-dind}' + privileged: true + command: ['--insecure-registry=registry:5000'] diff --git a/components/cli/e2e/stack/main_test.go b/components/cli/e2e/stack/main_test.go new file mode 100644 index 0000000000..74081f457b --- /dev/null +++ b/components/cli/e2e/stack/main_test.go @@ -0,0 +1,26 @@ +package stack + +import ( + "fmt" + "os" + "testing" + + "github.com/pkg/errors" +) + +func TestMain(m *testing.M) { + if err := setupTestEnv(); err != nil { + fmt.Println(err.Error()) + os.Exit(3) + } + os.Exit(m.Run()) +} + +// TODO: move to shared internal package +func setupTestEnv() error { + dockerHost := os.Getenv("TEST_DOCKER_HOST") + if dockerHost == "" { + return errors.New("$TEST_DOCKER_HOST must be set") + } + return os.Setenv("DOCKER_HOST", dockerHost) +} diff --git a/components/cli/e2e/stack/remove_test.go b/components/cli/e2e/stack/remove_test.go new file mode 100644 index 0000000000..77d95c793d --- /dev/null +++ b/components/cli/e2e/stack/remove_test.go @@ -0,0 +1,89 @@ +package stack + +import ( + "fmt" + "strings" + "testing" + "time" + + shlex "github.com/flynn-archive/go-shlex" + "github.com/gotestyourself/gotestyourself/golden" + "github.com/gotestyourself/gotestyourself/icmd" + "github.com/stretchr/testify/require" +) + +func TestRemove(t *testing.T) { + stackname := "test-stack-remove" + deployFullStack(t, stackname) + defer cleanupFullStack(t, stackname) + + result := icmd.RunCmd(shell(t, "docker stack rm %s", stackname)) + + result.Assert(t, icmd.Expected{Out: icmd.None}) + golden.Assert(t, result.Stderr(), "stack-remove-success.golden") +} + +func deployFullStack(t *testing.T, stackname string) { + // TODO: this stack should have full options not minimal options + result := icmd.RunCmd(shell(t, + "docker stack deploy --compose-file=./testdata/full-stack.yml %s", stackname)) + result.Assert(t, icmd.Success) + + waitOn(t, taskCount(stackname, 2), 0) +} + +func cleanupFullStack(t *testing.T, stackname string) { + result := icmd.RunCmd(shell(t, "docker stack rm %s", stackname)) + result.Assert(t, icmd.Success) + waitOn(t, taskCount(stackname, 0), 0) +} + +func taskCount(stackname string, expected int) func() (bool, error) { + return func() (bool, error) { + result := icmd.RunCommand( + "docker", "stack", "ps", "-f=desired-state=running", stackname) + count := lines(result.Stdout()) - 1 + return count == expected, nil + } +} + +func lines(out string) int { + return len(strings.Split(strings.TrimSpace(out), "\n")) +} + +// TODO: move to gotestyourself +func shell(t *testing.T, format string, args ...interface{}) icmd.Cmd { + cmd, err := shlex.Split(fmt.Sprintf(format, args...)) + require.NoError(t, err) + return icmd.Cmd{Command: cmd} +} + +// TODO: move to gotestyourself +func waitOn(t *testing.T, check func() (bool, error), timeout time.Duration) { + if timeout == time.Duration(0) { + timeout = defaultTimeout() + } + + after := time.After(timeout) + for { + select { + case <-after: + // TODO: include check function name in error message + t.Fatalf("timeout hit after %s", timeout) + default: + // TODO: maybe return a failure message as well? + done, err := check() + if done { + return + } + if err != nil { + t.Fatal(err.Error()) + } + } + } +} + +func defaultTimeout() time.Duration { + // TODO: support override from environment variable + return 10 * time.Second +} diff --git a/components/cli/e2e/stack/testdata/full-stack.yml b/components/cli/e2e/stack/testdata/full-stack.yml new file mode 100644 index 0000000000..8c4d06f854 --- /dev/null +++ b/components/cli/e2e/stack/testdata/full-stack.yml @@ -0,0 +1,9 @@ +version: '3.3' + +services: + one: + image: registry:5000/alpine:3.6 + command: top + two: + image: registry:5000/alpine:3.6 + command: top diff --git a/components/cli/e2e/stack/testdata/stack-remove-success.golden b/components/cli/e2e/stack/testdata/stack-remove-success.golden new file mode 100644 index 0000000000..f41a891702 --- /dev/null +++ b/components/cli/e2e/stack/testdata/stack-remove-success.golden @@ -0,0 +1,3 @@ +Removing service test-stack-remove_one +Removing service test-stack-remove_two +Removing network test-stack-remove_default diff --git a/components/cli/cli/internal/test/builders/config.go b/components/cli/internal/test/builders/config.go similarity index 100% rename from components/cli/cli/internal/test/builders/config.go rename to components/cli/internal/test/builders/config.go diff --git a/components/cli/cli/internal/test/builders/doc.go b/components/cli/internal/test/builders/doc.go similarity index 100% rename from components/cli/cli/internal/test/builders/doc.go rename to components/cli/internal/test/builders/doc.go diff --git a/components/cli/cli/internal/test/builders/node.go b/components/cli/internal/test/builders/node.go similarity index 100% rename from components/cli/cli/internal/test/builders/node.go rename to components/cli/internal/test/builders/node.go diff --git a/components/cli/cli/internal/test/builders/secret.go b/components/cli/internal/test/builders/secret.go similarity index 100% rename from components/cli/cli/internal/test/builders/secret.go rename to components/cli/internal/test/builders/secret.go diff --git a/components/cli/cli/internal/test/builders/service.go b/components/cli/internal/test/builders/service.go similarity index 100% rename from components/cli/cli/internal/test/builders/service.go rename to components/cli/internal/test/builders/service.go diff --git a/components/cli/cli/internal/test/builders/swarm.go b/components/cli/internal/test/builders/swarm.go similarity index 100% rename from components/cli/cli/internal/test/builders/swarm.go rename to components/cli/internal/test/builders/swarm.go diff --git a/components/cli/cli/internal/test/builders/task.go b/components/cli/internal/test/builders/task.go similarity index 100% rename from components/cli/cli/internal/test/builders/task.go rename to components/cli/internal/test/builders/task.go diff --git a/components/cli/cli/internal/test/builders/volume.go b/components/cli/internal/test/builders/volume.go similarity index 100% rename from components/cli/cli/internal/test/builders/volume.go rename to components/cli/internal/test/builders/volume.go diff --git a/components/cli/cli/internal/test/cli.go b/components/cli/internal/test/cli.go similarity index 90% rename from components/cli/cli/internal/test/cli.go rename to components/cli/internal/test/cli.go index 54b3bc83b1..0b3ee28978 100644 --- a/components/cli/cli/internal/test/cli.go +++ b/components/cli/internal/test/cli.go @@ -23,14 +23,6 @@ type FakeCli struct { server command.ServerInfo } -// NewFakeCliWithOutput returns a Cli backed by the fakeCli -// Deprecated: Use NewFakeCli -func NewFakeCliWithOutput(client client.APIClient, out io.Writer) *FakeCli { - cli := NewFakeCli(client) - cli.out = command.NewOutStream(out) - return cli -} - // NewFakeCli returns a fake for the command.Cli interface func NewFakeCli(client client.APIClient) *FakeCli { outBuffer := new(bytes.Buffer) diff --git a/components/cli/cli/internal/test/doc.go b/components/cli/internal/test/doc.go similarity index 85% rename from components/cli/cli/internal/test/doc.go rename to components/cli/internal/test/doc.go index 3a8609716f..342441d533 100644 --- a/components/cli/cli/internal/test/doc.go +++ b/components/cli/internal/test/doc.go @@ -1,5 +1,5 @@ // Package test is a test-only package that can be used by other cli package to write unit test. // -// It as an internal package and cannot be used outside of github.com/docker/cli/cli package. +// It as an internal package and cannot be used outside of github.com/docker/cli package. // package test diff --git a/components/cli/cli/internal/test/network/client.go b/components/cli/internal/test/network/client.go similarity index 100% rename from components/cli/cli/internal/test/network/client.go rename to components/cli/internal/test/network/client.go diff --git a/components/cli/cli/internal/test/store.go b/components/cli/internal/test/store.go similarity index 100% rename from components/cli/cli/internal/test/store.go rename to components/cli/internal/test/store.go diff --git a/components/cli/internal/test/testutil/assert.go b/components/cli/internal/test/testutil/assert.go new file mode 100644 index 0000000000..cf5d2c9876 --- /dev/null +++ b/components/cli/internal/test/testutil/assert.go @@ -0,0 +1,15 @@ +package testutil + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// ErrorContains checks that the error is not nil, and contains the expected +// substring. +// TODO: replace with testify if https://github.com/stretchr/testify/pull/486 +// is accepted. +func ErrorContains(t require.TestingT, err error, expectedError string) { + require.Error(t, err) + assert.Contains(t, err.Error(), expectedError) +} diff --git a/components/cli/man/docker-run.1.md b/components/cli/man/docker-run.1.md index 2a743c134e..6dbe8945b2 100644 --- a/components/cli/man/docker-run.1.md +++ b/components/cli/man/docker-run.1.md @@ -671,14 +671,14 @@ alphanumeric character, followed by `a-z0-9`, `_` (underscore), `.` (period) or If you supply a `HOST-DIR` that is an absolute path, Docker bind-mounts to the path you specify. If you supply a `name`, Docker creates a named volume by that `name`. For example, you can specify either `/foo` or `foo` for a `HOST-DIR` -value. If you supply the `/foo` value, Docker creates a bind-mount. If you +value. If you supply the `/foo` value, Docker creates a bind mount. If you supply the `foo` specification, Docker creates a named volume. You can specify multiple **-v** options to mount one or more mounts to a container. To use these same mounts in other containers, specify the **--volumes-from** option also. -You can supply additional options for each bind-mount following an additional +You can supply additional options for each bind mount following an additional colon. A `:ro` or `:rw` suffix mounts a volume in read-only or read-write mode, respectively. By default, volumes are mounted in read-write mode. You can also specify the consistency requirement for the mount, either diff --git a/components/cli/man/dockerd.8.md b/components/cli/man/dockerd.8.md index 8a7619ca2c..733cc20f88 100644 --- a/components/cli/man/dockerd.8.md +++ b/components/cli/man/dockerd.8.md @@ -23,6 +23,7 @@ dockerd - Enable daemon mode [**--default-gateway**[=*DEFAULT-GATEWAY*]] [**--default-gateway-v6**[=*DEFAULT-GATEWAY-V6*]] [**--default-runtime**[=*runc*]] +[**--default-ipc-mode**=*MODE*] [**--default-shm-size**[=*64MiB*]] [**--default-ulimit**[=*[]*]] [**--disable-legacy-registry**] @@ -185,6 +186,10 @@ $ sudo dockerd --add-runtime runc=runc --add-runtime custom=/usr/local/bin/my-ru **--default-runtime**="runc" Set default runtime if there're more than one specified by `--add-runtime`. +**--default-ipc-mode**="**private**|**shareable**" + Set the default IPC mode for newly created containers. The argument + can either be **private** or **shareable**. + **--default-shm-size**=*64MiB* Set the daemon-wide default shm size for containers. Default is `64MiB`. diff --git a/components/cli/man/src/container/create.md b/components/cli/man/src/container/create.md index e47bb38db1..8df663a8a6 100644 --- a/components/cli/man/src/container/create.md +++ b/components/cli/man/src/container/create.md @@ -16,14 +16,14 @@ alphanumeric character, followed by `a-z0-9`, `_` (underscore), `.` (period) or If you supply a `HOST-DIR` that is an absolute path, Docker bind-mounts to the path you specify. If you supply a `name`, Docker creates a named volume by that `name`. For example, you can specify either `/foo` or `foo` for a `HOST-DIR` -value. If you supply the `/foo` value, Docker creates a bind-mount. If you +value. If you supply the `/foo` value, Docker creates a bind mount. If you supply the `foo` specification, Docker creates a named volume. You can specify multiple **-v** options to mount one or more mounts to a container. To use these same mounts in other containers, specify the **--volumes-from** option also. -You can supply additional options for each bind-mount following an additional +You can supply additional options for each bind mount following an additional colon. A `:ro` or `:rw` suffix mounts a volume in read-only or read-write mode, respectively. By default, volumes are mounted in read-write mode. You can also specify the consistency requirement for the mount, either diff --git a/components/cli/opts/mount_test.go b/components/cli/opts/mount_test.go index 72aaa6258e..aba18a226f 100644 --- a/components/cli/opts/mount_test.go +++ b/components/cli/opts/mount_test.go @@ -4,8 +4,8 @@ import ( "os" "testing" + "github.com/docker/cli/internal/test/testutil" mounttypes "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/pkg/testutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) diff --git a/components/cli/opts/network_test.go b/components/cli/opts/network_test.go index ac0b2115a1..d19823dc1d 100644 --- a/components/cli/opts/network_test.go +++ b/components/cli/opts/network_test.go @@ -3,7 +3,7 @@ package opts import ( "testing" - "github.com/docker/docker/pkg/testutil" + "github.com/docker/cli/internal/test/testutil" "github.com/stretchr/testify/assert" ) diff --git a/components/cli/opts/port_test.go b/components/cli/opts/port_test.go index f84b58453b..fb4ffe47c2 100644 --- a/components/cli/opts/port_test.go +++ b/components/cli/opts/port_test.go @@ -3,8 +3,8 @@ package opts import ( "testing" + "github.com/docker/cli/internal/test/testutil" "github.com/docker/docker/api/types/swarm" - "github.com/docker/docker/pkg/testutil" "github.com/stretchr/testify/assert" ) diff --git a/components/cli/scripts/test/e2e/load-alpine b/components/cli/scripts/test/e2e/load-alpine new file mode 100755 index 0000000000..5b75f0989c --- /dev/null +++ b/components/cli/scripts/test/e2e/load-alpine @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -eu -o pipefail + +src=alpine:3.6 +dest=registry:5000/alpine:3.6 +docker pull $src +docker tag $src $dest +docker push $dest diff --git a/components/cli/scripts/test/e2e/run b/components/cli/scripts/test/e2e/run new file mode 100755 index 0000000000..936b5898fd --- /dev/null +++ b/components/cli/scripts/test/e2e/run @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# Run integration tests against the latest docker-ce dind +set -eu -o pipefail + +function container_ip { + local cid=$1 + local network=$2 + docker inspect \ + -f "{{.NetworkSettings.Networks.${network}.IPAddress}}" "$cid" +} + +function setup { + local project=$1 + COMPOSE_PROJECT_NAME=$1 COMPOSE_FILE=$2 docker-compose up -d >&2 + + local network="${project}_default" + # TODO: only run if inside a container + docker network connect "$network" "$(hostname)" + + engine_ip="$(container_ip "${project}_engine_1" "$network")" + engine_host="tcp://$engine_ip:2375" + ( + export DOCKER_HOST="$engine_host" + timeout -t 200 ./scripts/test/e2e/wait-on-daemon + ./scripts/test/e2e/load-alpine + is_swarm_enabled || docker swarm init + ) >&2 + echo "$engine_host" +} + +function is_swarm_enabled { + docker info 2> /dev/null | grep -q 'Swarm: active' +} + +function cleanup { + COMPOSE_PROJECT_NAME=$1 COMPOSE_FILE=$2 docker-compose down >&2 +} + +function runtests { + local engine_host=$1 + + env -i \ + TEST_DOCKER_HOST="$engine_host" \ + GOPATH="$GOPATH" \ + PATH="$PWD/build/" \ + "$(which go)" test -v ./e2e/... +} + +export unique_id="${E2E_UNIQUE_ID:-cliendtoendsuite}" +compose_env_file=./e2e/compose-env.yaml + +cmd=${1-} + +case "$cmd" in + setup) + setup "$unique_id" "$compose_env_file" + exit + ;; + cleanup) + cleanup "$unique_id" "$compose_env_file" + exit + ;; + test) + engine_host=${2-} + if [[ -z "${engine_host}" ]]; then + echo "missing parameter docker engine host" + echo "Usage: $0 test ENGINE_HOST" + exit 3 + fi + runtests "$engine_host" + ;; + run|"") + engine_host="$(setup "$unique_id" "$compose_env_file")" + testexit=0 + runtests "$engine_host" || testexit=$? + cleanup "$unique_id" "$compose_env_file" + exit $testexit + ;; + *) + echo "Unknown command: $cmd" + echo "Usage: " + echo " $0 [setup | cleanup | test | run] [engine_host]" + exit 1 + ;; +esac diff --git a/components/cli/scripts/test/e2e/wait-on-daemon b/components/cli/scripts/test/e2e/wait-on-daemon new file mode 100755 index 0000000000..d1dd5c39f2 --- /dev/null +++ b/components/cli/scripts/test/e2e/wait-on-daemon @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -eu -o pipefail + +echo "Waiting for docker daemon to become available at $DOCKER_HOST" +while ! docker version > /dev/null; do + sleep 0.3 +done + +docker version diff --git a/components/cli/scripts/test/e2e/wrapper b/components/cli/scripts/test/e2e/wrapper new file mode 100755 index 0000000000..a3a4f00b67 --- /dev/null +++ b/components/cli/scripts/test/e2e/wrapper @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Setup, run and teardown e2e test suite in containers. +set -eu -o pipefail + +unique_id="${E2E_UNIQUE_ID:-cliendtoendsuite}" +e2e_env_image=docker-cli-e2e-env:$unique_id +dev_image=docker-cli-dev:$unique_id + +function run_in_env { + local cmd=$1 + docker run -i --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -e E2E_UNIQUE_ID \ + "$e2e_env_image" "$cmd" +} + +docker build \ + -t "$e2e_env_image" \ + -f dockerfiles/Dockerfile.test-e2e-env . + +docker build \ + -t "$dev_image" \ + -f dockerfiles/Dockerfile.dev . + +engine_host=$(run_in_env setup) +testexit=0 +docker run -i --rm \ + -v "$PWD:/go/src/github.com/docker/cli" \ + --network "${unique_id}_default" \ + "$dev_image" \ + ./scripts/test/e2e/run test "$engine_host" || testexit="$?" +run_in_env cleanup +exit "$testexit" diff --git a/components/cli/scripts/test/watch b/components/cli/scripts/test/watch index 6c9745aead..264eb7c249 100755 --- a/components/cli/scripts/test/watch +++ b/components/cli/scripts/test/watch @@ -1,3 +1,3 @@ #!/bin/sh # shellcheck disable=SC2016 -exec filewatcher -L 6 -x build -x script go test -timeout 10s -v './${dir}' +exec filewatcher -L 6 -x build -x script go test -timeout 30s -v './${dir}' diff --git a/components/cli/scripts/warn-outside-container b/components/cli/scripts/warn-outside-container new file mode 100755 index 0000000000..cb11b8fa8d --- /dev/null +++ b/components/cli/scripts/warn-outside-container @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -eu + +target="${1:-}" + +if [[ -z "${DISABLE_WARN_OUTSIDE_CONTAINER:-}" ]]; then + ( + echo + echo + echo "WARNING: you are not in a container." + echo "Use \"make -f docker.Makefile $target\" or set" + echo "DISABLE_WARN_OUTSIDE_CONTAINER=1 to disable this warning." + echo + echo "Press Ctrl+C now to abort." + echo + ) >&2 + sleep 10 +fi diff --git a/components/cli/vendor.conf b/components/cli/vendor.conf index 1e9e72ff43..04fc658912 100755 --- a/components/cli/vendor.conf +++ b/components/cli/vendor.conf @@ -22,6 +22,7 @@ github.com/docker/swarmkit 79381d0840be27f8b3f5c667b348a4467d866eeb github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff github.com/gogo/protobuf 7efa791bd276fd4db00867cbd982b552627c24cb github.com/golang/protobuf 7a211bcf3bce0e3f1d74f9894916e6f116ae83b4 +github.com/gotestyourself/gotestyourself v1.0.0 github.com/gorilla/context v1.1 github.com/gorilla/mux v1.1 github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 @@ -31,7 +32,6 @@ github.com/mitchellh/mapstructure f3009df150dadf309fdee4a54ed65c124afad715 github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb github.com/opencontainers/image-spec f03dbe35d449c54915d235f1a3cf8f585a24babe github.com/opencontainers/runc 9c2d8d184e5da67c95d601382adf14862e4f2228 https://github.com/docker/runc.git -github.com/opencontainers/selinux v1.0.0-rc1 github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9 github.com/pmezard/go-difflib v1.0.0 github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438 diff --git a/components/cli/vendor/github.com/docker/docker/pkg/parsers/parsers.go b/components/cli/vendor/github.com/docker/docker/pkg/parsers/parsers.go deleted file mode 100644 index acc897168f..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/parsers/parsers.go +++ /dev/null @@ -1,69 +0,0 @@ -// Package parsers provides helper functions to parse and validate different type -// of string. It can be hosts, unix addresses, tcp addresses, filters, kernel -// operating system versions. -package parsers - -import ( - "fmt" - "strconv" - "strings" -) - -// ParseKeyValueOpt parses and validates the specified string as a key/value pair (key=value) -func ParseKeyValueOpt(opt string) (string, string, error) { - parts := strings.SplitN(opt, "=", 2) - if len(parts) != 2 { - return "", "", fmt.Errorf("Unable to parse key/value option: %s", opt) - } - return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil -} - -// ParseUintList parses and validates the specified string as the value -// found in some cgroup file (e.g. `cpuset.cpus`, `cpuset.mems`), which could be -// one of the formats below. Note that duplicates are actually allowed in the -// input string. It returns a `map[int]bool` with available elements from `val` -// set to `true`. -// Supported formats: -// 7 -// 1-6 -// 0,3-4,7,8-10 -// 0-0,0,1-7 -// 03,1-3 <- this is gonna get parsed as [1,2,3] -// 3,2,1 -// 0-2,3,1 -func ParseUintList(val string) (map[int]bool, error) { - if val == "" { - return map[int]bool{}, nil - } - - availableInts := make(map[int]bool) - split := strings.Split(val, ",") - errInvalidFormat := fmt.Errorf("invalid format: %s", val) - - for _, r := range split { - if !strings.Contains(r, "-") { - v, err := strconv.Atoi(r) - if err != nil { - return nil, errInvalidFormat - } - availableInts[v] = true - } else { - split := strings.SplitN(r, "-", 2) - min, err := strconv.Atoi(split[0]) - if err != nil { - return nil, errInvalidFormat - } - max, err := strconv.Atoi(split[1]) - if err != nil { - return nil, errInvalidFormat - } - if max < min { - return nil, errInvalidFormat - } - for i := min; i <= max; i++ { - availableInts[i] = true - } - } - } - return availableInts, nil -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/README.md b/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/README.md deleted file mode 100644 index c1530cef0d..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/README.md +++ /dev/null @@ -1 +0,0 @@ -SysInfo stores information about which features a kernel supports. diff --git a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/numcpu.go b/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/numcpu.go deleted file mode 100644 index aeb1a3a804..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/numcpu.go +++ /dev/null @@ -1,12 +0,0 @@ -// +build !linux,!windows - -package sysinfo - -import ( - "runtime" -) - -// NumCPU returns the number of CPUs -func NumCPU() int { - return runtime.NumCPU() -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/numcpu_linux.go b/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/numcpu_linux.go deleted file mode 100644 index 5eacd35121..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/numcpu_linux.go +++ /dev/null @@ -1,43 +0,0 @@ -// +build linux - -package sysinfo - -import ( - "runtime" - "syscall" - "unsafe" -) - -// numCPU queries the system for the count of threads available -// for use to this process. -// -// Issues two syscalls. -// Returns 0 on errors. Use |runtime.NumCPU| in that case. -func numCPU() int { - // Gets the affinity mask for a process: The very one invoking this function. - pid, _, _ := syscall.RawSyscall(syscall.SYS_GETPID, 0, 0, 0) - - var mask [1024 / 64]uintptr - _, _, err := syscall.RawSyscall(syscall.SYS_SCHED_GETAFFINITY, pid, uintptr(len(mask)*8), uintptr(unsafe.Pointer(&mask[0]))) - if err != 0 { - return 0 - } - - // For every available thread a bit is set in the mask. - ncpu := 0 - for _, e := range mask { - if e == 0 { - continue - } - ncpu += int(popcnt(uint64(e))) - } - return ncpu -} - -// NumCPU returns the number of CPUs which are currently online -func NumCPU() int { - if ncpu := numCPU(); ncpu > 0 { - return ncpu - } - return runtime.NumCPU() -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/numcpu_windows.go b/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/numcpu_windows.go deleted file mode 100644 index 1d89dd5503..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/numcpu_windows.go +++ /dev/null @@ -1,37 +0,0 @@ -// +build windows - -package sysinfo - -import ( - "runtime" - "unsafe" - - "golang.org/x/sys/windows" -) - -var ( - kernel32 = windows.NewLazySystemDLL("kernel32.dll") - getCurrentProcess = kernel32.NewProc("GetCurrentProcess") - getProcessAffinityMask = kernel32.NewProc("GetProcessAffinityMask") -) - -func numCPU() int { - // Gets the affinity mask for a process - var mask, sysmask uintptr - currentProcess, _, _ := getCurrentProcess.Call() - ret, _, _ := getProcessAffinityMask.Call(currentProcess, uintptr(unsafe.Pointer(&mask)), uintptr(unsafe.Pointer(&sysmask))) - if ret == 0 { - return 0 - } - // For every available thread a bit is set in the mask. - ncpu := int(popcnt(uint64(mask))) - return ncpu -} - -// NumCPU returns the number of CPUs which are currently online -func NumCPU() int { - if ncpu := numCPU(); ncpu > 0 { - return ncpu - } - return runtime.NumCPU() -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo.go b/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo.go deleted file mode 100644 index f046de4b16..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo.go +++ /dev/null @@ -1,144 +0,0 @@ -package sysinfo - -import "github.com/docker/docker/pkg/parsers" - -// SysInfo stores information about which features a kernel supports. -// TODO Windows: Factor out platform specific capabilities. -type SysInfo struct { - // Whether the kernel supports AppArmor or not - AppArmor bool - // Whether the kernel supports Seccomp or not - Seccomp bool - - cgroupMemInfo - cgroupCPUInfo - cgroupBlkioInfo - cgroupCpusetInfo - cgroupPids - - // Whether IPv4 forwarding is supported or not, if this was disabled, networking will not work - IPv4ForwardingDisabled bool - - // Whether bridge-nf-call-iptables is supported or not - BridgeNFCallIPTablesDisabled bool - - // Whether bridge-nf-call-ip6tables is supported or not - BridgeNFCallIP6TablesDisabled bool - - // Whether the cgroup has the mountpoint of "devices" or not - CgroupDevicesEnabled bool -} - -type cgroupMemInfo struct { - // Whether memory limit is supported or not - MemoryLimit bool - - // Whether swap limit is supported or not - SwapLimit bool - - // Whether soft limit is supported or not - MemoryReservation bool - - // Whether OOM killer disable is supported or not - OomKillDisable bool - - // Whether memory swappiness is supported or not - MemorySwappiness bool - - // Whether kernel memory limit is supported or not - KernelMemory bool -} - -type cgroupCPUInfo struct { - // Whether CPU shares is supported or not - CPUShares bool - - // Whether CPU CFS(Completely Fair Scheduler) period is supported or not - CPUCfsPeriod bool - - // Whether CPU CFS(Completely Fair Scheduler) quota is supported or not - CPUCfsQuota bool - - // Whether CPU real-time period is supported or not - CPURealtimePeriod bool - - // Whether CPU real-time runtime is supported or not - CPURealtimeRuntime bool -} - -type cgroupBlkioInfo struct { - // Whether Block IO weight is supported or not - BlkioWeight bool - - // Whether Block IO weight_device is supported or not - BlkioWeightDevice bool - - // Whether Block IO read limit in bytes per second is supported or not - BlkioReadBpsDevice bool - - // Whether Block IO write limit in bytes per second is supported or not - BlkioWriteBpsDevice bool - - // Whether Block IO read limit in IO per second is supported or not - BlkioReadIOpsDevice bool - - // Whether Block IO write limit in IO per second is supported or not - BlkioWriteIOpsDevice bool -} - -type cgroupCpusetInfo struct { - // Whether Cpuset is supported or not - Cpuset bool - - // Available Cpuset's cpus - Cpus string - - // Available Cpuset's memory nodes - Mems string -} - -type cgroupPids struct { - // Whether Pids Limit is supported or not - PidsLimit bool -} - -// IsCpusetCpusAvailable returns `true` if the provided string set is contained -// in cgroup's cpuset.cpus set, `false` otherwise. -// If error is not nil a parsing error occurred. -func (c cgroupCpusetInfo) IsCpusetCpusAvailable(provided string) (bool, error) { - return isCpusetListAvailable(provided, c.Cpus) -} - -// IsCpusetMemsAvailable returns `true` if the provided string set is contained -// in cgroup's cpuset.mems set, `false` otherwise. -// If error is not nil a parsing error occurred. -func (c cgroupCpusetInfo) IsCpusetMemsAvailable(provided string) (bool, error) { - return isCpusetListAvailable(provided, c.Mems) -} - -func isCpusetListAvailable(provided, available string) (bool, error) { - parsedProvided, err := parsers.ParseUintList(provided) - if err != nil { - return false, err - } - parsedAvailable, err := parsers.ParseUintList(available) - if err != nil { - return false, err - } - for k := range parsedProvided { - if !parsedAvailable[k] { - return false, nil - } - } - return true, nil -} - -// Returns bit count of 1, used by NumCPU -func popcnt(x uint64) (n byte) { - x -= (x >> 1) & 0x5555555555555555 - x = (x>>2)&0x3333333333333333 + x&0x3333333333333333 - x += x >> 4 - x &= 0x0f0f0f0f0f0f0f0f - x *= 0x0101010101010101 - return byte(x >> 56) -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_linux.go b/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_linux.go deleted file mode 100644 index 7ad84a8309..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_linux.go +++ /dev/null @@ -1,259 +0,0 @@ -package sysinfo - -import ( - "fmt" - "io/ioutil" - "os" - "path" - "strings" - "syscall" - - "github.com/Sirupsen/logrus" - "github.com/opencontainers/runc/libcontainer/cgroups" -) - -const ( - // SeccompModeFilter refers to the syscall argument SECCOMP_MODE_FILTER. - SeccompModeFilter = uintptr(2) -) - -func findCgroupMountpoints() (map[string]string, error) { - cgMounts, err := cgroups.GetCgroupMounts(false) - if err != nil { - return nil, fmt.Errorf("Failed to parse cgroup information: %v", err) - } - mps := make(map[string]string) - for _, m := range cgMounts { - for _, ss := range m.Subsystems { - mps[ss] = m.Mountpoint - } - } - return mps, nil -} - -// New returns a new SysInfo, using the filesystem to detect which features -// the kernel supports. If `quiet` is `false` warnings are printed in logs -// whenever an error occurs or misconfigurations are present. -func New(quiet bool) *SysInfo { - sysInfo := &SysInfo{} - cgMounts, err := findCgroupMountpoints() - if err != nil { - logrus.Warnf("Failed to parse cgroup information: %v", err) - } else { - sysInfo.cgroupMemInfo = checkCgroupMem(cgMounts, quiet) - sysInfo.cgroupCPUInfo = checkCgroupCPU(cgMounts, quiet) - sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet) - sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet) - sysInfo.cgroupPids = checkCgroupPids(quiet) - } - - _, ok := cgMounts["devices"] - sysInfo.CgroupDevicesEnabled = ok - - sysInfo.IPv4ForwardingDisabled = !readProcBool("/proc/sys/net/ipv4/ip_forward") - sysInfo.BridgeNFCallIPTablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-iptables") - sysInfo.BridgeNFCallIP6TablesDisabled = !readProcBool("/proc/sys/net/bridge/bridge-nf-call-ip6tables") - - // Check if AppArmor is supported. - if _, err := os.Stat("/sys/kernel/security/apparmor"); !os.IsNotExist(err) { - sysInfo.AppArmor = true - } - - // Check if Seccomp is supported, via CONFIG_SECCOMP. - if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_GET_SECCOMP, 0, 0); err != syscall.EINVAL { - // Make sure the kernel has CONFIG_SECCOMP_FILTER. - if _, _, err := syscall.RawSyscall(syscall.SYS_PRCTL, syscall.PR_SET_SECCOMP, SeccompModeFilter, 0); err != syscall.EINVAL { - sysInfo.Seccomp = true - } - } - - return sysInfo -} - -// checkCgroupMem reads the memory information from the memory cgroup mount point. -func checkCgroupMem(cgMounts map[string]string, quiet bool) cgroupMemInfo { - mountPoint, ok := cgMounts["memory"] - if !ok { - if !quiet { - logrus.Warn("Your kernel does not support cgroup memory limit") - } - return cgroupMemInfo{} - } - - swapLimit := cgroupEnabled(mountPoint, "memory.memsw.limit_in_bytes") - if !quiet && !swapLimit { - logrus.Warn("Your kernel does not support swap memory limit") - } - memoryReservation := cgroupEnabled(mountPoint, "memory.soft_limit_in_bytes") - if !quiet && !memoryReservation { - logrus.Warn("Your kernel does not support memory reservation") - } - oomKillDisable := cgroupEnabled(mountPoint, "memory.oom_control") - if !quiet && !oomKillDisable { - logrus.Warn("Your kernel does not support oom control") - } - memorySwappiness := cgroupEnabled(mountPoint, "memory.swappiness") - if !quiet && !memorySwappiness { - logrus.Warn("Your kernel does not support memory swappiness") - } - kernelMemory := cgroupEnabled(mountPoint, "memory.kmem.limit_in_bytes") - if !quiet && !kernelMemory { - logrus.Warn("Your kernel does not support kernel memory limit") - } - - return cgroupMemInfo{ - MemoryLimit: true, - SwapLimit: swapLimit, - MemoryReservation: memoryReservation, - OomKillDisable: oomKillDisable, - MemorySwappiness: memorySwappiness, - KernelMemory: kernelMemory, - } -} - -// checkCgroupCPU reads the cpu information from the cpu cgroup mount point. -func checkCgroupCPU(cgMounts map[string]string, quiet bool) cgroupCPUInfo { - mountPoint, ok := cgMounts["cpu"] - if !ok { - if !quiet { - logrus.Warn("Unable to find cpu cgroup in mounts") - } - return cgroupCPUInfo{} - } - - cpuShares := cgroupEnabled(mountPoint, "cpu.shares") - if !quiet && !cpuShares { - logrus.Warn("Your kernel does not support cgroup cpu shares") - } - - cpuCfsPeriod := cgroupEnabled(mountPoint, "cpu.cfs_period_us") - if !quiet && !cpuCfsPeriod { - logrus.Warn("Your kernel does not support cgroup cfs period") - } - - cpuCfsQuota := cgroupEnabled(mountPoint, "cpu.cfs_quota_us") - if !quiet && !cpuCfsQuota { - logrus.Warn("Your kernel does not support cgroup cfs quotas") - } - - cpuRealtimePeriod := cgroupEnabled(mountPoint, "cpu.rt_period_us") - if !quiet && !cpuRealtimePeriod { - logrus.Warn("Your kernel does not support cgroup rt period") - } - - cpuRealtimeRuntime := cgroupEnabled(mountPoint, "cpu.rt_runtime_us") - if !quiet && !cpuRealtimeRuntime { - logrus.Warn("Your kernel does not support cgroup rt runtime") - } - - return cgroupCPUInfo{ - CPUShares: cpuShares, - CPUCfsPeriod: cpuCfsPeriod, - CPUCfsQuota: cpuCfsQuota, - CPURealtimePeriod: cpuRealtimePeriod, - CPURealtimeRuntime: cpuRealtimeRuntime, - } -} - -// checkCgroupBlkioInfo reads the blkio information from the blkio cgroup mount point. -func checkCgroupBlkioInfo(cgMounts map[string]string, quiet bool) cgroupBlkioInfo { - mountPoint, ok := cgMounts["blkio"] - if !ok { - if !quiet { - logrus.Warn("Unable to find blkio cgroup in mounts") - } - return cgroupBlkioInfo{} - } - - weight := cgroupEnabled(mountPoint, "blkio.weight") - if !quiet && !weight { - logrus.Warn("Your kernel does not support cgroup blkio weight") - } - - weightDevice := cgroupEnabled(mountPoint, "blkio.weight_device") - if !quiet && !weightDevice { - logrus.Warn("Your kernel does not support cgroup blkio weight_device") - } - - readBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_bps_device") - if !quiet && !readBpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.read_bps_device") - } - - writeBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_bps_device") - if !quiet && !writeBpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.write_bps_device") - } - readIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_iops_device") - if !quiet && !readIOpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.read_iops_device") - } - - writeIOpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_iops_device") - if !quiet && !writeIOpsDevice { - logrus.Warn("Your kernel does not support cgroup blkio throttle.write_iops_device") - } - return cgroupBlkioInfo{ - BlkioWeight: weight, - BlkioWeightDevice: weightDevice, - BlkioReadBpsDevice: readBpsDevice, - BlkioWriteBpsDevice: writeBpsDevice, - BlkioReadIOpsDevice: readIOpsDevice, - BlkioWriteIOpsDevice: writeIOpsDevice, - } -} - -// checkCgroupCpusetInfo reads the cpuset information from the cpuset cgroup mount point. -func checkCgroupCpusetInfo(cgMounts map[string]string, quiet bool) cgroupCpusetInfo { - mountPoint, ok := cgMounts["cpuset"] - if !ok { - if !quiet { - logrus.Warn("Unable to find cpuset cgroup in mounts") - } - return cgroupCpusetInfo{} - } - - cpus, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.cpus")) - if err != nil { - return cgroupCpusetInfo{} - } - - mems, err := ioutil.ReadFile(path.Join(mountPoint, "cpuset.mems")) - if err != nil { - return cgroupCpusetInfo{} - } - - return cgroupCpusetInfo{ - Cpuset: true, - Cpus: strings.TrimSpace(string(cpus)), - Mems: strings.TrimSpace(string(mems)), - } -} - -// checkCgroupPids reads the pids information from the pids cgroup mount point. -func checkCgroupPids(quiet bool) cgroupPids { - _, err := cgroups.FindCgroupMountpoint("pids") - if err != nil { - if !quiet { - logrus.Warn(err) - } - return cgroupPids{} - } - - return cgroupPids{ - PidsLimit: true, - } -} - -func cgroupEnabled(mountPoint, name string) bool { - _, err := os.Stat(path.Join(mountPoint, name)) - return err == nil -} - -func readProcBool(path string) bool { - val, err := ioutil.ReadFile(path) - if err != nil { - return false - } - return strings.TrimSpace(string(val)) == "1" -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_solaris.go b/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_solaris.go deleted file mode 100644 index c858d57e08..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_solaris.go +++ /dev/null @@ -1,121 +0,0 @@ -// +build solaris,cgo - -package sysinfo - -import ( - "bytes" - "os/exec" - "strconv" - "strings" -) - -/* -#cgo LDFLAGS: -llgrp -#include -#include -#include -int getLgrpCount() { - lgrp_cookie_t lgrpcookie = LGRP_COOKIE_NONE; - uint_t nlgrps; - - if ((lgrpcookie = lgrp_init(LGRP_VIEW_OS)) == LGRP_COOKIE_NONE) { - return -1; - } - nlgrps = lgrp_nlgrps(lgrpcookie); - return nlgrps; -} -*/ -import "C" - -// IsCPUSharesAvailable returns whether CPUShares setting is supported. -// We need FSS to be set as default scheduling class to support CPU Shares -func IsCPUSharesAvailable() bool { - cmd := exec.Command("/usr/sbin/dispadmin", "-d") - outBuf := new(bytes.Buffer) - errBuf := new(bytes.Buffer) - cmd.Stderr = errBuf - cmd.Stdout = outBuf - - if err := cmd.Run(); err != nil { - return false - } - return (strings.Contains(outBuf.String(), "FSS")) -} - -// New returns a new SysInfo, using the filesystem to detect which features -// the kernel supports. -//NOTE Solaris: If we change the below capabilities be sure -// to update verifyPlatformContainerSettings() in daemon_solaris.go -func New(quiet bool) *SysInfo { - sysInfo := &SysInfo{} - sysInfo.cgroupMemInfo = setCgroupMem(quiet) - sysInfo.cgroupCPUInfo = setCgroupCPU(quiet) - sysInfo.cgroupBlkioInfo = setCgroupBlkioInfo(quiet) - sysInfo.cgroupCpusetInfo = setCgroupCPUsetInfo(quiet) - - sysInfo.IPv4ForwardingDisabled = false - - sysInfo.AppArmor = false - - return sysInfo -} - -// setCgroupMem reads the memory information for Solaris. -func setCgroupMem(quiet bool) cgroupMemInfo { - - return cgroupMemInfo{ - MemoryLimit: true, - SwapLimit: true, - MemoryReservation: false, - OomKillDisable: false, - MemorySwappiness: false, - KernelMemory: false, - } -} - -// setCgroupCPU reads the cpu information for Solaris. -func setCgroupCPU(quiet bool) cgroupCPUInfo { - - return cgroupCPUInfo{ - CPUShares: true, - CPUCfsPeriod: false, - CPUCfsQuota: true, - CPURealtimePeriod: false, - CPURealtimeRuntime: false, - } -} - -// blkio switches are not supported in Solaris. -func setCgroupBlkioInfo(quiet bool) cgroupBlkioInfo { - - return cgroupBlkioInfo{ - BlkioWeight: false, - BlkioWeightDevice: false, - } -} - -// setCgroupCPUsetInfo reads the cpuset information for Solaris. -func setCgroupCPUsetInfo(quiet bool) cgroupCpusetInfo { - - return cgroupCpusetInfo{ - Cpuset: true, - Cpus: getCPUCount(), - Mems: getLgrpCount(), - } -} - -func getCPUCount() string { - ncpus := C.sysconf(C._SC_NPROCESSORS_ONLN) - if ncpus <= 0 { - return "" - } - return strconv.FormatInt(int64(ncpus), 16) -} - -func getLgrpCount() string { - nlgrps := C.getLgrpCount() - if nlgrps <= 0 { - return "" - } - return strconv.FormatInt(int64(nlgrps), 16) -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_unix.go b/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_unix.go deleted file mode 100644 index 45f3ef1c65..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_unix.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build !linux,!solaris,!windows - -package sysinfo - -// New returns an empty SysInfo for non linux nor solaris for now. -func New(quiet bool) *SysInfo { - sysInfo := &SysInfo{} - return sysInfo -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_windows.go b/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_windows.go deleted file mode 100644 index 4e6255bc59..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/sysinfo/sysinfo_windows.go +++ /dev/null @@ -1,9 +0,0 @@ -// +build windows - -package sysinfo - -// New returns an empty SysInfo for windows for now. -func New(quiet bool) *SysInfo { - sysInfo := &SysInfo{} - return sysInfo -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/testutil/golden/golden.go b/components/cli/vendor/github.com/docker/docker/pkg/testutil/golden/golden.go deleted file mode 100644 index 8f725da7bf..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/testutil/golden/golden.go +++ /dev/null @@ -1,28 +0,0 @@ -// Package golden provides function and helpers to use golden file for -// testing purpose. -package golden - -import ( - "flag" - "io/ioutil" - "path/filepath" - "testing" -) - -var update = flag.Bool("test.update", false, "update golden file") - -// Get returns the golden file content. If the `test.update` is specified, it updates the -// file with the current output and returns it. -func Get(t *testing.T, actual []byte, filename string) []byte { - golden := filepath.Join("testdata", filename) - if *update { - if err := ioutil.WriteFile(golden, actual, 0644); err != nil { - t.Fatal(err) - } - } - expected, err := ioutil.ReadFile(golden) - if err != nil { - t.Fatal(err) - } - return expected -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/testutil/helpers.go b/components/cli/vendor/github.com/docker/docker/pkg/testutil/helpers.go deleted file mode 100644 index c291148712..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/testutil/helpers.go +++ /dev/null @@ -1,33 +0,0 @@ -package testutil - -import ( - "strings" - "unicode" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -// ErrorContains checks that the error is not nil, and contains the expected -// substring. -func ErrorContains(t require.TestingT, err error, expectedError string) { - require.Error(t, err) - assert.Contains(t, err.Error(), expectedError) -} - -// EqualNormalizedString compare the actual value to the expected value after applying the specified -// transform function. It fails the test if these two transformed string are not equal. -// For example `EqualNormalizedString(t, RemoveSpace, "foo\n", "foo")` wouldn't fail the test as -// spaces (and thus '\n') are removed before comparing the string. -func EqualNormalizedString(t require.TestingT, transformFun func(rune) rune, actual, expected string) { - require.Equal(t, strings.Map(transformFun, expected), strings.Map(transformFun, actual)) -} - -// RemoveSpace returns -1 if the specified runes is considered as a space (unicode) -// and the rune itself otherwise. -func RemoveSpace(r rune) rune { - if unicode.IsSpace(r) { - return -1 - } - return r -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/testutil/pkg.go b/components/cli/vendor/github.com/docker/docker/pkg/testutil/pkg.go deleted file mode 100644 index 110b2e6a79..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/testutil/pkg.go +++ /dev/null @@ -1 +0,0 @@ -package testutil diff --git a/components/cli/vendor/github.com/docker/docker/pkg/testutil/tempfile/tempfile.go b/components/cli/vendor/github.com/docker/docker/pkg/testutil/tempfile/tempfile.go deleted file mode 100644 index 01474babff..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/testutil/tempfile/tempfile.go +++ /dev/null @@ -1,56 +0,0 @@ -package tempfile - -import ( - "io/ioutil" - "os" - - "github.com/stretchr/testify/require" -) - -// TempFile is a temporary file that can be used with unit tests. TempFile -// reduces the boilerplate setup required in each test case by handling -// setup errors. -type TempFile struct { - File *os.File -} - -// NewTempFile returns a new temp file with contents -func NewTempFile(t require.TestingT, prefix string, content string) *TempFile { - file, err := ioutil.TempFile("", prefix+"-") - require.NoError(t, err) - - _, err = file.Write([]byte(content)) - require.NoError(t, err) - file.Close() - return &TempFile{File: file} -} - -// Name returns the filename -func (f *TempFile) Name() string { - return f.File.Name() -} - -// Remove removes the file -func (f *TempFile) Remove() { - os.Remove(f.Name()) -} - -// TempDir is a temporary directory that can be used with unit tests. TempDir -// reduces the boilerplate setup required in each test case by handling -// setup errors. -type TempDir struct { - Path string -} - -// NewTempDir returns a new temp file with contents -func NewTempDir(t require.TestingT, prefix string) *TempDir { - path, err := ioutil.TempDir("", prefix+"-") - require.NoError(t, err) - - return &TempDir{Path: path} -} - -// Remove removes the file -func (f *TempDir) Remove() { - os.Remove(f.Path) -} diff --git a/components/cli/vendor/github.com/docker/docker/pkg/testutil/utils.go b/components/cli/vendor/github.com/docker/docker/pkg/testutil/utils.go deleted file mode 100644 index 0522dde2b1..0000000000 --- a/components/cli/vendor/github.com/docker/docker/pkg/testutil/utils.go +++ /dev/null @@ -1,218 +0,0 @@ -package testutil - -import ( - "archive/tar" - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "reflect" - "strings" - "syscall" - "time" - - "github.com/docker/docker/pkg/stringutils" - "github.com/docker/docker/pkg/system" -) - -// IsKilled process the specified error and returns whether the process was killed or not. -func IsKilled(err error) bool { - if exitErr, ok := err.(*exec.ExitError); ok { - status, ok := exitErr.Sys().(syscall.WaitStatus) - if !ok { - return false - } - // status.ExitStatus() is required on Windows because it does not - // implement Signal() nor Signaled(). Just check it had a bad exit - // status could mean it was killed (and in tests we do kill) - return (status.Signaled() && status.Signal() == os.Kill) || status.ExitStatus() != 0 - } - return false -} - -func runCommandWithOutput(cmd *exec.Cmd) (output string, exitCode int, err error) { - out, err := cmd.CombinedOutput() - exitCode = system.ProcessExitCode(err) - output = string(out) - return -} - -// RunCommandPipelineWithOutput runs the array of commands with the output -// of each pipelined with the following (like cmd1 | cmd2 | cmd3 would do). -// It returns the final output, the exitCode different from 0 and the error -// if something bad happened. -func RunCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode int, err error) { - if len(cmds) < 2 { - return "", 0, errors.New("pipeline does not have multiple cmds") - } - - // connect stdin of each cmd to stdout pipe of previous cmd - for i, cmd := range cmds { - if i > 0 { - prevCmd := cmds[i-1] - cmd.Stdin, err = prevCmd.StdoutPipe() - - if err != nil { - return "", 0, fmt.Errorf("cannot set stdout pipe for %s: %v", cmd.Path, err) - } - } - } - - // start all cmds except the last - for _, cmd := range cmds[:len(cmds)-1] { - if err = cmd.Start(); err != nil { - return "", 0, fmt.Errorf("starting %s failed with error: %v", cmd.Path, err) - } - } - - defer func() { - var pipeErrMsgs []string - // wait all cmds except the last to release their resources - for _, cmd := range cmds[:len(cmds)-1] { - if pipeErr := cmd.Wait(); pipeErr != nil { - pipeErrMsgs = append(pipeErrMsgs, fmt.Sprintf("command %s failed with error: %v", cmd.Path, pipeErr)) - } - } - if len(pipeErrMsgs) > 0 && err == nil { - err = fmt.Errorf("pipelineError from Wait: %v", strings.Join(pipeErrMsgs, ", ")) - } - }() - - // wait on last cmd - return runCommandWithOutput(cmds[len(cmds)-1]) -} - -// ConvertSliceOfStringsToMap converts a slices of string in a map -// with the strings as key and an empty string as values. -func ConvertSliceOfStringsToMap(input []string) map[string]struct{} { - output := make(map[string]struct{}) - for _, v := range input { - output[v] = struct{}{} - } - return output -} - -// CompareDirectoryEntries compares two sets of FileInfo (usually taken from a directory) -// and returns an error if different. -func CompareDirectoryEntries(e1 []os.FileInfo, e2 []os.FileInfo) error { - var ( - e1Entries = make(map[string]struct{}) - e2Entries = make(map[string]struct{}) - ) - for _, e := range e1 { - e1Entries[e.Name()] = struct{}{} - } - for _, e := range e2 { - e2Entries[e.Name()] = struct{}{} - } - if !reflect.DeepEqual(e1Entries, e2Entries) { - return fmt.Errorf("entries differ") - } - return nil -} - -// ListTar lists the entries of a tar. -func ListTar(f io.Reader) ([]string, error) { - tr := tar.NewReader(f) - var entries []string - - for { - th, err := tr.Next() - if err == io.EOF { - // end of tar archive - return entries, nil - } - if err != nil { - return entries, err - } - entries = append(entries, th.Name) - } -} - -// RandomTmpDirPath provides a temporary path with rand string appended. -// does not create or checks if it exists. -func RandomTmpDirPath(s string, platform string) string { - tmp := "/tmp" - if platform == "windows" { - tmp = os.Getenv("TEMP") - } - path := filepath.Join(tmp, fmt.Sprintf("%s.%s", s, stringutils.GenerateRandomAlphaOnlyString(10))) - if platform == "windows" { - return filepath.FromSlash(path) // Using \ - } - return filepath.ToSlash(path) // Using / -} - -// ConsumeWithSpeed reads chunkSize bytes from reader before sleeping -// for interval duration. Returns total read bytes. Send true to the -// stop channel to return before reading to EOF on the reader. -func ConsumeWithSpeed(reader io.Reader, chunkSize int, interval time.Duration, stop chan bool) (n int, err error) { - buffer := make([]byte, chunkSize) - for { - var readBytes int - readBytes, err = reader.Read(buffer) - n += readBytes - if err != nil { - if err == io.EOF { - err = nil - } - return - } - select { - case <-stop: - return - case <-time.After(interval): - } - } -} - -// ParseCgroupPaths parses 'procCgroupData', which is output of '/proc//cgroup', and returns -// a map which cgroup name as key and path as value. -func ParseCgroupPaths(procCgroupData string) map[string]string { - cgroupPaths := map[string]string{} - for _, line := range strings.Split(procCgroupData, "\n") { - parts := strings.Split(line, ":") - if len(parts) != 3 { - continue - } - cgroupPaths[parts[1]] = parts[2] - } - return cgroupPaths -} - -// ChannelBuffer holds a chan of byte array that can be populate in a goroutine. -type ChannelBuffer struct { - C chan []byte -} - -// Write implements Writer. -func (c *ChannelBuffer) Write(b []byte) (int, error) { - c.C <- b - return len(b), nil -} - -// Close closes the go channel. -func (c *ChannelBuffer) Close() error { - close(c.C) - return nil -} - -// ReadTimeout reads the content of the channel in the specified byte array with -// the specified duration as timeout. -func (c *ChannelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) { - select { - case b := <-c.C: - return copy(p[0:], b), nil - case <-time.After(n): - return -1, fmt.Errorf("timeout reading from channel") - } -} - -// ReadBody read the specified ReadCloser content and returns it -func ReadBody(b io.ReadCloser) ([]byte, error) { - defer b.Close() - return ioutil.ReadAll(b) -} diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/config.go b/components/cli/vendor/github.com/docker/docker/runconfig/config.go deleted file mode 100644 index c9dc6e96ea..0000000000 --- a/components/cli/vendor/github.com/docker/docker/runconfig/config.go +++ /dev/null @@ -1,108 +0,0 @@ -package runconfig - -import ( - "encoding/json" - "fmt" - "io" - - "github.com/docker/docker/api/types/container" - networktypes "github.com/docker/docker/api/types/network" - "github.com/docker/docker/pkg/sysinfo" - "github.com/docker/docker/volume" -) - -// ContainerDecoder implements httputils.ContainerDecoder -// calling DecodeContainerConfig. -type ContainerDecoder struct{} - -// DecodeConfig makes ContainerDecoder to implement httputils.ContainerDecoder -func (r ContainerDecoder) DecodeConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { - return DecodeContainerConfig(src) -} - -// DecodeHostConfig makes ContainerDecoder to implement httputils.ContainerDecoder -func (r ContainerDecoder) DecodeHostConfig(src io.Reader) (*container.HostConfig, error) { - return DecodeHostConfig(src) -} - -// DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper -// struct and returns both a Config and a HostConfig struct -// Be aware this function is not checking whether the resulted structs are nil, -// it's your business to do so -func DecodeContainerConfig(src io.Reader) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) { - var w ContainerConfigWrapper - - decoder := json.NewDecoder(src) - if err := decoder.Decode(&w); err != nil { - return nil, nil, nil, err - } - - hc := w.getHostConfig() - - // Perform platform-specific processing of Volumes and Binds. - if w.Config != nil && hc != nil { - - // Initialize the volumes map if currently nil - if w.Config.Volumes == nil { - w.Config.Volumes = make(map[string]struct{}) - } - - // Now validate all the volumes and binds - if err := validateMountSettings(w.Config, hc); err != nil { - return nil, nil, nil, err - } - } - - // Certain parameters need daemon-side validation that cannot be done - // on the client, as only the daemon knows what is valid for the platform. - if err := validateNetMode(w.Config, hc); err != nil { - return nil, nil, nil, err - } - - // Validate isolation - if err := validateIsolation(hc); err != nil { - return nil, nil, nil, err - } - - // Validate QoS - if err := validateQoS(hc); err != nil { - return nil, nil, nil, err - } - - // Validate Resources - if err := validateResources(hc, sysinfo.New(true)); err != nil { - return nil, nil, nil, err - } - - // Validate Privileged - if err := validatePrivileged(hc); err != nil { - return nil, nil, nil, err - } - - // Validate ReadonlyRootfs - if err := validateReadonlyRootfs(hc); err != nil { - return nil, nil, nil, err - } - - return w.Config, hc, w.NetworkingConfig, nil -} - -// validateMountSettings validates each of the volumes and bind settings -// passed by the caller to ensure they are valid. -func validateMountSettings(c *container.Config, hc *container.HostConfig) error { - // it is ok to have len(hc.Mounts) > 0 && (len(hc.Binds) > 0 || len (c.Volumes) > 0 || len (hc.Tmpfs) > 0 ) - - // Ensure all volumes and binds are valid. - for spec := range c.Volumes { - if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil { - return fmt.Errorf("invalid volume spec %q: %v", spec, err) - } - } - for _, spec := range hc.Binds { - if _, err := volume.ParseMountRaw(spec, hc.VolumeDriver); err != nil { - return fmt.Errorf("invalid bind mount spec %q: %v", spec, err) - } - } - - return nil -} diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/config_unix.go b/components/cli/vendor/github.com/docker/docker/runconfig/config_unix.go deleted file mode 100644 index b4fbfb2799..0000000000 --- a/components/cli/vendor/github.com/docker/docker/runconfig/config_unix.go +++ /dev/null @@ -1,59 +0,0 @@ -// +build !windows - -package runconfig - -import ( - "github.com/docker/docker/api/types/container" - networktypes "github.com/docker/docker/api/types/network" -) - -// ContainerConfigWrapper is a Config wrapper that holds the container Config (portable) -// and the corresponding HostConfig (non-portable). -type ContainerConfigWrapper struct { - *container.Config - InnerHostConfig *container.HostConfig `json:"HostConfig,omitempty"` - Cpuset string `json:",omitempty"` // Deprecated. Exported for backwards compatibility. - NetworkingConfig *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"` - *container.HostConfig // Deprecated. Exported to read attributes from json that are not in the inner host config structure. -} - -// getHostConfig gets the HostConfig of the Config. -// It's mostly there to handle Deprecated fields of the ContainerConfigWrapper -func (w *ContainerConfigWrapper) getHostConfig() *container.HostConfig { - hc := w.HostConfig - - if hc == nil && w.InnerHostConfig != nil { - hc = w.InnerHostConfig - } else if w.InnerHostConfig != nil { - if hc.Memory != 0 && w.InnerHostConfig.Memory == 0 { - w.InnerHostConfig.Memory = hc.Memory - } - if hc.MemorySwap != 0 && w.InnerHostConfig.MemorySwap == 0 { - w.InnerHostConfig.MemorySwap = hc.MemorySwap - } - if hc.CPUShares != 0 && w.InnerHostConfig.CPUShares == 0 { - w.InnerHostConfig.CPUShares = hc.CPUShares - } - if hc.CpusetCpus != "" && w.InnerHostConfig.CpusetCpus == "" { - w.InnerHostConfig.CpusetCpus = hc.CpusetCpus - } - - if hc.VolumeDriver != "" && w.InnerHostConfig.VolumeDriver == "" { - w.InnerHostConfig.VolumeDriver = hc.VolumeDriver - } - - hc = w.InnerHostConfig - } - - if hc != nil { - if w.Cpuset != "" && hc.CpusetCpus == "" { - hc.CpusetCpus = w.Cpuset - } - } - - // Make sure NetworkMode has an acceptable value. We do this to ensure - // backwards compatible API behavior. - SetDefaultNetModeIfBlank(hc) - - return hc -} diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/config_windows.go b/components/cli/vendor/github.com/docker/docker/runconfig/config_windows.go deleted file mode 100644 index f2361b554b..0000000000 --- a/components/cli/vendor/github.com/docker/docker/runconfig/config_windows.go +++ /dev/null @@ -1,19 +0,0 @@ -package runconfig - -import ( - "github.com/docker/docker/api/types/container" - networktypes "github.com/docker/docker/api/types/network" -) - -// ContainerConfigWrapper is a Config wrapper that holds the container Config (portable) -// and the corresponding HostConfig (non-portable). -type ContainerConfigWrapper struct { - *container.Config - HostConfig *container.HostConfig `json:"HostConfig,omitempty"` - NetworkingConfig *networktypes.NetworkingConfig `json:"NetworkingConfig,omitempty"` -} - -// getHostConfig gets the HostConfig of the Config. -func (w *ContainerConfigWrapper) getHostConfig() *container.HostConfig { - return w.HostConfig -} diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/errors.go b/components/cli/vendor/github.com/docker/docker/runconfig/errors.go deleted file mode 100644 index c95a2919e8..0000000000 --- a/components/cli/vendor/github.com/docker/docker/runconfig/errors.go +++ /dev/null @@ -1,38 +0,0 @@ -package runconfig - -import ( - "fmt" -) - -var ( - // ErrConflictContainerNetworkAndLinks conflict between --net=container and links - ErrConflictContainerNetworkAndLinks = fmt.Errorf("conflicting options: container type network can't be used with links. This would result in undefined behavior") - // ErrConflictSharedNetwork conflict between private and other networks - ErrConflictSharedNetwork = fmt.Errorf("container sharing network namespace with another container or host cannot be connected to any other network") - // ErrConflictHostNetwork conflict from being disconnected from host network or connected to host network. - ErrConflictHostNetwork = fmt.Errorf("container cannot be disconnected from host network or connected to host network") - // ErrConflictNoNetwork conflict between private and other networks - ErrConflictNoNetwork = fmt.Errorf("container cannot be connected to multiple networks with one of the networks in private (none) mode") - // ErrConflictNetworkAndDNS conflict between --dns and the network mode - ErrConflictNetworkAndDNS = fmt.Errorf("conflicting options: dns and the network mode") - // ErrConflictNetworkHostname conflict between the hostname and the network mode - ErrConflictNetworkHostname = fmt.Errorf("conflicting options: hostname and the network mode") - // ErrConflictHostNetworkAndLinks conflict between --net=host and links - ErrConflictHostNetworkAndLinks = fmt.Errorf("conflicting options: host type networking can't be used with links. This would result in undefined behavior") - // ErrConflictContainerNetworkAndMac conflict between the mac address and the network mode - ErrConflictContainerNetworkAndMac = fmt.Errorf("conflicting options: mac-address and the network mode") - // ErrConflictNetworkHosts conflict between add-host and the network mode - ErrConflictNetworkHosts = fmt.Errorf("conflicting options: custom host-to-IP mapping and the network mode") - // ErrConflictNetworkPublishPorts conflict between the publish options and the network mode - ErrConflictNetworkPublishPorts = fmt.Errorf("conflicting options: port publishing and the container type network mode") - // ErrConflictNetworkExposePorts conflict between the expose option and the network mode - ErrConflictNetworkExposePorts = fmt.Errorf("conflicting options: port exposing and the container type network mode") - // ErrUnsupportedNetworkAndIP conflict between network mode and requested ip address - ErrUnsupportedNetworkAndIP = fmt.Errorf("user specified IP address is supported on user defined networks only") - // ErrUnsupportedNetworkNoSubnetAndIP conflict between network with no configured subnet and requested ip address - ErrUnsupportedNetworkNoSubnetAndIP = fmt.Errorf("user specified IP address is supported only when connecting to networks with user configured subnets") - // ErrUnsupportedNetworkAndAlias conflict between network mode and alias - ErrUnsupportedNetworkAndAlias = fmt.Errorf("network-scoped alias is supported only for containers in user defined networks") - // ErrConflictUTSHostname conflict between the hostname and the UTS mode - ErrConflictUTSHostname = fmt.Errorf("conflicting options: hostname and the UTS mode") -) diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig.go b/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig.go deleted file mode 100644 index 24aed1935e..0000000000 --- a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig.go +++ /dev/null @@ -1,80 +0,0 @@ -package runconfig - -import ( - "encoding/json" - "fmt" - "io" - "strings" - - "github.com/docker/docker/api/types/container" -) - -// DecodeHostConfig creates a HostConfig based on the specified Reader. -// It assumes the content of the reader will be JSON, and decodes it. -func DecodeHostConfig(src io.Reader) (*container.HostConfig, error) { - decoder := json.NewDecoder(src) - - var w ContainerConfigWrapper - if err := decoder.Decode(&w); err != nil { - return nil, err - } - - hc := w.getHostConfig() - return hc, nil -} - -// SetDefaultNetModeIfBlank changes the NetworkMode in a HostConfig structure -// to default if it is not populated. This ensures backwards compatibility after -// the validation of the network mode was moved from the docker CLI to the -// docker daemon. -func SetDefaultNetModeIfBlank(hc *container.HostConfig) { - if hc != nil { - if hc.NetworkMode == container.NetworkMode("") { - hc.NetworkMode = container.NetworkMode("default") - } - } -} - -// validateNetContainerMode ensures that the various combinations of requested -// network settings wrt container mode are valid. -func validateNetContainerMode(c *container.Config, hc *container.HostConfig) error { - // We may not be passed a host config, such as in the case of docker commit - if hc == nil { - return nil - } - parts := strings.Split(string(hc.NetworkMode), ":") - if parts[0] == "container" { - if len(parts) < 2 || parts[1] == "" { - return fmt.Errorf("Invalid network mode: invalid container format container:") - } - } - - if hc.NetworkMode.IsContainer() && c.Hostname != "" { - return ErrConflictNetworkHostname - } - - if hc.NetworkMode.IsContainer() && len(hc.Links) > 0 { - return ErrConflictContainerNetworkAndLinks - } - - if hc.NetworkMode.IsContainer() && len(hc.DNS) > 0 { - return ErrConflictNetworkAndDNS - } - - if hc.NetworkMode.IsContainer() && len(hc.ExtraHosts) > 0 { - return ErrConflictNetworkHosts - } - - if (hc.NetworkMode.IsContainer() || hc.NetworkMode.IsHost()) && c.MacAddress != "" { - return ErrConflictContainerNetworkAndMac - } - - if hc.NetworkMode.IsContainer() && (len(hc.PortBindings) > 0 || hc.PublishAllPorts == true) { - return ErrConflictNetworkPublishPorts - } - - if hc.NetworkMode.IsContainer() && len(c.ExposedPorts) > 0 { - return ErrConflictNetworkExposePorts - } - return nil -} diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_solaris.go b/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_solaris.go deleted file mode 100644 index 5b6e13dc9d..0000000000 --- a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_solaris.go +++ /dev/null @@ -1,46 +0,0 @@ -package runconfig - -import ( - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/pkg/sysinfo" -) - -// DefaultDaemonNetworkMode returns the default network stack the daemon should -// use. -func DefaultDaemonNetworkMode() container.NetworkMode { - return container.NetworkMode("bridge") -} - -// IsPreDefinedNetwork indicates if a network is predefined by the daemon -func IsPreDefinedNetwork(network string) bool { - return false -} - -// validateNetMode ensures that the various combinations of requested -// network settings are valid. -func validateNetMode(c *container.Config, hc *container.HostConfig) error { - // We may not be passed a host config, such as in the case of docker commit - return nil -} - -// validateIsolation performs platform specific validation of the -// isolation level in the hostconfig structure. -// This setting is currently discarded for Solaris so this is a no-op. -func validateIsolation(hc *container.HostConfig) error { - return nil -} - -// validateQoS performs platform specific validation of the QoS settings -func validateQoS(hc *container.HostConfig) error { - return nil -} - -// validateResources performs platform specific validation of the resource settings -func validateResources(hc *container.HostConfig, si *sysinfo.SysInfo) error { - return nil -} - -// validatePrivileged performs platform specific validation of the Privileged setting -func validatePrivileged(hc *container.HostConfig) error { - return nil -} diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_unix.go b/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_unix.go deleted file mode 100644 index 55df5da3ff..0000000000 --- a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_unix.go +++ /dev/null @@ -1,110 +0,0 @@ -// +build !windows,!solaris - -package runconfig - -import ( - "fmt" - "runtime" - - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/pkg/sysinfo" -) - -// DefaultDaemonNetworkMode returns the default network stack the daemon should -// use. -func DefaultDaemonNetworkMode() container.NetworkMode { - return container.NetworkMode("bridge") -} - -// IsPreDefinedNetwork indicates if a network is predefined by the daemon -func IsPreDefinedNetwork(network string) bool { - n := container.NetworkMode(network) - return n.IsBridge() || n.IsHost() || n.IsNone() || n.IsDefault() -} - -// validateNetMode ensures that the various combinations of requested -// network settings are valid. -func validateNetMode(c *container.Config, hc *container.HostConfig) error { - // We may not be passed a host config, such as in the case of docker commit - if hc == nil { - return nil - } - - err := validateNetContainerMode(c, hc) - if err != nil { - return err - } - - if hc.UTSMode.IsHost() && c.Hostname != "" { - return ErrConflictUTSHostname - } - - if hc.NetworkMode.IsHost() && len(hc.Links) > 0 { - return ErrConflictHostNetworkAndLinks - } - - return nil -} - -// validateIsolation performs platform specific validation of -// isolation in the hostconfig structure. Linux only supports "default" -// which is LXC container isolation -func validateIsolation(hc *container.HostConfig) error { - // We may not be passed a host config, such as in the case of docker commit - if hc == nil { - return nil - } - if !hc.Isolation.IsValid() { - return fmt.Errorf("Invalid isolation: %q - %s only supports 'default'", hc.Isolation, runtime.GOOS) - } - return nil -} - -// validateQoS performs platform specific validation of the QoS settings -func validateQoS(hc *container.HostConfig) error { - // We may not be passed a host config, such as in the case of docker commit - if hc == nil { - return nil - } - - if hc.IOMaximumBandwidth != 0 { - return fmt.Errorf("Invalid QoS settings: %s does not support configuration of maximum bandwidth", runtime.GOOS) - } - - if hc.IOMaximumIOps != 0 { - return fmt.Errorf("Invalid QoS settings: %s does not support configuration of maximum IOPs", runtime.GOOS) - } - return nil -} - -// validateResources performs platform specific validation of the resource settings -// cpu-rt-runtime and cpu-rt-period can not be greater than their parent, cpu-rt-runtime requires sys_nice -func validateResources(hc *container.HostConfig, si *sysinfo.SysInfo) error { - // We may not be passed a host config, such as in the case of docker commit - if hc == nil { - return nil - } - - if hc.Resources.CPURealtimePeriod > 0 && !si.CPURealtimePeriod { - return fmt.Errorf("Your kernel does not support cgroup cpu real-time period") - } - - if hc.Resources.CPURealtimeRuntime > 0 && !si.CPURealtimeRuntime { - return fmt.Errorf("Your kernel does not support cgroup cpu real-time runtime") - } - - if hc.Resources.CPURealtimePeriod != 0 && hc.Resources.CPURealtimeRuntime != 0 && hc.Resources.CPURealtimeRuntime > hc.Resources.CPURealtimePeriod { - return fmt.Errorf("cpu real-time runtime cannot be higher than cpu real-time period") - } - return nil -} - -// validatePrivileged performs platform specific validation of the Privileged setting -func validatePrivileged(hc *container.HostConfig) error { - return nil -} - -// validateReadonlyRootfs performs platform specific validation of the ReadonlyRootfs setting -func validateReadonlyRootfs(hc *container.HostConfig) error { - return nil -} diff --git a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_windows.go b/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_windows.go deleted file mode 100644 index 5eb956d1b4..0000000000 --- a/components/cli/vendor/github.com/docker/docker/runconfig/hostconfig_windows.go +++ /dev/null @@ -1,96 +0,0 @@ -package runconfig - -import ( - "fmt" - - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/pkg/sysinfo" -) - -// DefaultDaemonNetworkMode returns the default network stack the daemon should -// use. -func DefaultDaemonNetworkMode() container.NetworkMode { - return container.NetworkMode("nat") -} - -// IsPreDefinedNetwork indicates if a network is predefined by the daemon -func IsPreDefinedNetwork(network string) bool { - return !container.NetworkMode(network).IsUserDefined() -} - -// validateNetMode ensures that the various combinations of requested -// network settings are valid. -func validateNetMode(c *container.Config, hc *container.HostConfig) error { - if hc == nil { - return nil - } - - err := validateNetContainerMode(c, hc) - if err != nil { - return err - } - - if hc.NetworkMode.IsContainer() && hc.Isolation.IsHyperV() { - return fmt.Errorf("Using the network stack of another container is not supported while using Hyper-V Containers") - } - - return nil -} - -// validateIsolation performs platform specific validation of the -// isolation in the hostconfig structure. Windows supports 'default' (or -// blank), 'process', or 'hyperv'. -func validateIsolation(hc *container.HostConfig) error { - // We may not be passed a host config, such as in the case of docker commit - if hc == nil { - return nil - } - if !hc.Isolation.IsValid() { - return fmt.Errorf("Invalid isolation: %q. Windows supports 'default', 'process', or 'hyperv'", hc.Isolation) - } - return nil -} - -// validateQoS performs platform specific validation of the Qos settings -func validateQoS(hc *container.HostConfig) error { - return nil -} - -// validateResources performs platform specific validation of the resource settings -func validateResources(hc *container.HostConfig, si *sysinfo.SysInfo) error { - // We may not be passed a host config, such as in the case of docker commit - if hc == nil { - return nil - } - if hc.Resources.CPURealtimePeriod != 0 { - return fmt.Errorf("Windows does not support CPU real-time period") - } - if hc.Resources.CPURealtimeRuntime != 0 { - return fmt.Errorf("Windows does not support CPU real-time runtime") - } - return nil -} - -// validatePrivileged performs platform specific validation of the Privileged setting -func validatePrivileged(hc *container.HostConfig) error { - // We may not be passed a host config, such as in the case of docker commit - if hc == nil { - return nil - } - if hc.Privileged { - return fmt.Errorf("Windows does not support privileged mode") - } - return nil -} - -// validateReadonlyRootfs performs platform specific validation of the ReadonlyRootfs setting -func validateReadonlyRootfs(hc *container.HostConfig) error { - // We may not be passed a host config, such as in the case of docker commit - if hc == nil { - return nil - } - if hc.ReadonlyRootfs { - return fmt.Errorf("Windows does not support root filesystem in read-only mode") - } - return nil -} diff --git a/components/cli/vendor/github.com/docker/docker/volume/validate.go b/components/cli/vendor/github.com/docker/docker/volume/validate.go deleted file mode 100644 index 42396a0dad..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/validate.go +++ /dev/null @@ -1,140 +0,0 @@ -package volume - -import ( - "errors" - "fmt" - "os" - "path/filepath" - - "github.com/docker/docker/api/types/mount" -) - -var errBindNotExist = errors.New("bind source path does not exist") - -type validateOpts struct { - skipBindSourceCheck bool - skipAbsolutePathCheck bool -} - -func validateMountConfig(mnt *mount.Mount, options ...func(*validateOpts)) error { - opts := validateOpts{} - for _, o := range options { - o(&opts) - } - - if len(mnt.Target) == 0 { - return &errMountConfig{mnt, errMissingField("Target")} - } - - if err := validateNotRoot(mnt.Target); err != nil { - return &errMountConfig{mnt, err} - } - - if !opts.skipAbsolutePathCheck { - if err := validateAbsolute(mnt.Target); err != nil { - return &errMountConfig{mnt, err} - } - } - - switch mnt.Type { - case mount.TypeBind: - if len(mnt.Source) == 0 { - return &errMountConfig{mnt, errMissingField("Source")} - } - // Don't error out just because the propagation mode is not supported on the platform - if opts := mnt.BindOptions; opts != nil { - if len(opts.Propagation) > 0 && len(propagationModes) > 0 { - if _, ok := propagationModes[opts.Propagation]; !ok { - return &errMountConfig{mnt, fmt.Errorf("invalid propagation mode: %s", opts.Propagation)} - } - } - } - if mnt.VolumeOptions != nil { - return &errMountConfig{mnt, errExtraField("VolumeOptions")} - } - - if err := validateAbsolute(mnt.Source); err != nil { - return &errMountConfig{mnt, err} - } - - // Do not allow binding to non-existent path - if !opts.skipBindSourceCheck { - fi, err := os.Stat(mnt.Source) - if err != nil { - if !os.IsNotExist(err) { - return &errMountConfig{mnt, err} - } - return &errMountConfig{mnt, errBindNotExist} - } - if err := validateStat(fi); err != nil { - return &errMountConfig{mnt, err} - } - } - case mount.TypeVolume: - if mnt.BindOptions != nil { - return &errMountConfig{mnt, errExtraField("BindOptions")} - } - - if len(mnt.Source) == 0 && mnt.ReadOnly { - return &errMountConfig{mnt, fmt.Errorf("must not set ReadOnly mode when using anonymous volumes")} - } - - if len(mnt.Source) != 0 { - if valid, err := IsVolumeNameValid(mnt.Source); !valid { - if err == nil { - err = errors.New("invalid volume name") - } - return &errMountConfig{mnt, err} - } - } - case mount.TypeTmpfs: - if len(mnt.Source) != 0 { - return &errMountConfig{mnt, errExtraField("Source")} - } - if err := ValidateTmpfsMountDestination(mnt.Target); err != nil { - return &errMountConfig{mnt, err} - } - if _, err := ConvertTmpfsOptions(mnt.TmpfsOptions, mnt.ReadOnly); err != nil { - return &errMountConfig{mnt, err} - } - default: - return &errMountConfig{mnt, errors.New("mount type unknown")} - } - return nil -} - -type errMountConfig struct { - mount *mount.Mount - err error -} - -func (e *errMountConfig) Error() string { - return fmt.Sprintf("invalid mount config for type %q: %v", e.mount.Type, e.err.Error()) -} - -func errExtraField(name string) error { - return fmt.Errorf("field %s must not be specified", name) -} -func errMissingField(name string) error { - return fmt.Errorf("field %s must not be empty", name) -} - -func validateAbsolute(p string) error { - p = convertSlash(p) - if filepath.IsAbs(p) { - return nil - } - return fmt.Errorf("invalid mount path: '%s' mount path must be absolute", p) -} - -// ValidateTmpfsMountDestination validates the destination of tmpfs mount. -// Currently, we have only two obvious rule for validation: -// - path must not be "/" -// - path must be absolute -// We should add more rules carefully (#30166) -func ValidateTmpfsMountDestination(dest string) error { - if err := validateNotRoot(dest); err != nil { - return err - } - return validateAbsolute(dest) -} diff --git a/components/cli/vendor/github.com/docker/docker/volume/validate_test_unix.go b/components/cli/vendor/github.com/docker/docker/volume/validate_test_unix.go deleted file mode 100644 index dd1de2f643..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/validate_test_unix.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build !windows - -package volume - -var ( - testDestinationPath = "/foo" - testSourcePath = "/foo" -) diff --git a/components/cli/vendor/github.com/docker/docker/volume/validate_test_windows.go b/components/cli/vendor/github.com/docker/docker/volume/validate_test_windows.go deleted file mode 100644 index d5f86ac850..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/validate_test_windows.go +++ /dev/null @@ -1,6 +0,0 @@ -package volume - -var ( - testDestinationPath = `c:\foo` - testSourcePath = `c:\foo` -) diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume.go b/components/cli/vendor/github.com/docker/docker/volume/volume.go deleted file mode 100644 index 8598d4cb8f..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/volume.go +++ /dev/null @@ -1,374 +0,0 @@ -package volume - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "syscall" - "time" - - mounttypes "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/pkg/idtools" - "github.com/docker/docker/pkg/stringid" - "github.com/opencontainers/selinux/go-selinux/label" - "github.com/pkg/errors" -) - -// DefaultDriverName is the driver name used for the driver -// implemented in the local package. -const DefaultDriverName = "local" - -// Scopes define if a volume has is cluster-wide (global) or local only. -// Scopes are returned by the volume driver when it is queried for capabilities and then set on a volume -const ( - LocalScope = "local" - GlobalScope = "global" -) - -// Driver is for creating and removing volumes. -type Driver interface { - // Name returns the name of the volume driver. - Name() string - // Create makes a new volume with the given name. - Create(name string, opts map[string]string) (Volume, error) - // Remove deletes the volume. - Remove(vol Volume) (err error) - // List lists all the volumes the driver has - List() ([]Volume, error) - // Get retrieves the volume with the requested name - Get(name string) (Volume, error) - // Scope returns the scope of the driver (e.g. `global` or `local`). - // Scope determines how the driver is handled at a cluster level - Scope() string -} - -// Capability defines a set of capabilities that a driver is able to handle. -type Capability struct { - // Scope is the scope of the driver, `global` or `local` - // A `global` scope indicates that the driver manages volumes across the cluster - // A `local` scope indicates that the driver only manages volumes resources local to the host - // Scope is declared by the driver - Scope string -} - -// Volume is a place to store data. It is backed by a specific driver, and can be mounted. -type Volume interface { - // Name returns the name of the volume - Name() string - // DriverName returns the name of the driver which owns this volume. - DriverName() string - // Path returns the absolute path to the volume. - Path() string - // Mount mounts the volume and returns the absolute path to - // where it can be consumed. - Mount(id string) (string, error) - // Unmount unmounts the volume when it is no longer in use. - Unmount(id string) error - // CreatedAt returns Volume Creation time - CreatedAt() (time.Time, error) - // Status returns low-level status information about a volume - Status() map[string]interface{} -} - -// DetailedVolume wraps a Volume with user-defined labels, options, and cluster scope (e.g., `local` or `global`) -type DetailedVolume interface { - Labels() map[string]string - Options() map[string]string - Scope() string - Volume -} - -// MountPoint is the intersection point between a volume and a container. It -// specifies which volume is to be used and where inside a container it should -// be mounted. -type MountPoint struct { - // Source is the source path of the mount. - // E.g. `mount --bind /foo /bar`, `/foo` is the `Source`. - Source string - // Destination is the path relative to the container root (`/`) to the mount point - // It is where the `Source` is mounted to - Destination string - // RW is set to true when the mountpoint should be mounted as read-write - RW bool - // Name is the name reference to the underlying data defined by `Source` - // e.g., the volume name - Name string - // Driver is the volume driver used to create the volume (if it is a volume) - Driver string - // Type of mount to use, see `Type` definitions in github.com/docker/docker/api/types/mount - Type mounttypes.Type `json:",omitempty"` - // Volume is the volume providing data to this mountpoint. - // This is nil unless `Type` is set to `TypeVolume` - Volume Volume `json:"-"` - - // Mode is the comma separated list of options supplied by the user when creating - // the bind/volume mount. - // Note Mode is not used on Windows - Mode string `json:"Relabel,omitempty"` // Originally field was `Relabel`" - - // Propagation describes how the mounts are propagated from the host into the - // mount point, and vice-versa. - // See https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt - // Note Propagation is not used on Windows - Propagation mounttypes.Propagation `json:",omitempty"` // Mount propagation string - - // Specifies if data should be copied from the container before the first mount - // Use a pointer here so we can tell if the user set this value explicitly - // This allows us to error out when the user explicitly enabled copy but we can't copy due to the volume being populated - CopyData bool `json:"-"` - // ID is the opaque ID used to pass to the volume driver. - // This should be set by calls to `Mount` and unset by calls to `Unmount` - ID string `json:",omitempty"` - - // Sepc is a copy of the API request that created this mount. - Spec mounttypes.Mount - - // Track usage of this mountpoint - // Specifically needed for containers which are running and calls to `docker cp` - // because both these actions require mounting the volumes. - active int -} - -// Cleanup frees resources used by the mountpoint -func (m *MountPoint) Cleanup() error { - if m.Volume == nil || m.ID == "" { - return nil - } - - if err := m.Volume.Unmount(m.ID); err != nil { - return errors.Wrapf(err, "error unmounting volume %s", m.Volume.Name()) - } - - m.active-- - if m.active == 0 { - m.ID = "" - } - return nil -} - -// Setup sets up a mount point by either mounting the volume if it is -// configured, or creating the source directory if supplied. -// The, optional, checkFun parameter allows doing additional checking -// before creating the source directory on the host. -func (m *MountPoint) Setup(mountLabel string, rootIDs idtools.IDPair, checkFun func(m *MountPoint) error) (path string, err error) { - defer func() { - if err != nil || !label.RelabelNeeded(m.Mode) { - return - } - - err = label.Relabel(m.Source, mountLabel, label.IsShared(m.Mode)) - if err == syscall.ENOTSUP { - err = nil - } - if err != nil { - path = "" - err = errors.Wrapf(err, "error setting label on mount source '%s'", m.Source) - } - }() - - if m.Volume != nil { - id := m.ID - if id == "" { - id = stringid.GenerateNonCryptoID() - } - path, err := m.Volume.Mount(id) - if err != nil { - return "", errors.Wrapf(err, "error while mounting volume '%s'", m.Source) - } - - m.ID = id - m.active++ - return path, nil - } - - if len(m.Source) == 0 { - return "", fmt.Errorf("Unable to setup mount point, neither source nor volume defined") - } - - // system.MkdirAll() produces an error if m.Source exists and is a file (not a directory), - if m.Type == mounttypes.TypeBind { - // Before creating the source directory on the host, invoke checkFun if it's not nil. One of - // the use case is to forbid creating the daemon socket as a directory if the daemon is in - // the process of shutting down. - if checkFun != nil { - if err := checkFun(m); err != nil { - return "", err - } - } - // idtools.MkdirAllNewAs() produces an error if m.Source exists and is a file (not a directory) - // also, makes sure that if the directory is created, the correct remapped rootUID/rootGID will own it - if err := idtools.MkdirAllAndChownNew(m.Source, 0755, rootIDs); err != nil { - if perr, ok := err.(*os.PathError); ok { - if perr.Err != syscall.ENOTDIR { - return "", errors.Wrapf(err, "error while creating mount source path '%s'", m.Source) - } - } - } - } - return m.Source, nil -} - -// Path returns the path of a volume in a mount point. -func (m *MountPoint) Path() string { - if m.Volume != nil { - return m.Volume.Path() - } - return m.Source -} - -// ParseVolumesFrom ensures that the supplied volumes-from is valid. -func ParseVolumesFrom(spec string) (string, string, error) { - if len(spec) == 0 { - return "", "", fmt.Errorf("volumes-from specification cannot be an empty string") - } - - specParts := strings.SplitN(spec, ":", 2) - id := specParts[0] - mode := "rw" - - if len(specParts) == 2 { - mode = specParts[1] - if !ValidMountMode(mode) { - return "", "", errInvalidMode(mode) - } - // For now don't allow propagation properties while importing - // volumes from data container. These volumes will inherit - // the same propagation property as of the original volume - // in data container. This probably can be relaxed in future. - if HasPropagation(mode) { - return "", "", errInvalidMode(mode) - } - // Do not allow copy modes on volumes-from - if _, isSet := getCopyMode(mode); isSet { - return "", "", errInvalidMode(mode) - } - } - return id, mode, nil -} - -// ParseMountRaw parses a raw volume spec (e.g. `-v /foo:/bar:shared`) into a -// structured spec. Once the raw spec is parsed it relies on `ParseMountSpec` to -// validate the spec and create a MountPoint -func ParseMountRaw(raw, volumeDriver string) (*MountPoint, error) { - arr, err := splitRawSpec(convertSlash(raw)) - if err != nil { - return nil, err - } - - var spec mounttypes.Mount - var mode string - switch len(arr) { - case 1: - // Just a destination path in the container - spec.Target = arr[0] - case 2: - if ValidMountMode(arr[1]) { - // Destination + Mode is not a valid volume - volumes - // cannot include a mode. e.g. /foo:rw - return nil, errInvalidSpec(raw) - } - // Host Source Path or Name + Destination - spec.Source = arr[0] - spec.Target = arr[1] - case 3: - // HostSourcePath+DestinationPath+Mode - spec.Source = arr[0] - spec.Target = arr[1] - mode = arr[2] - default: - return nil, errInvalidSpec(raw) - } - - if !ValidMountMode(mode) { - return nil, errInvalidMode(mode) - } - - if filepath.IsAbs(spec.Source) { - spec.Type = mounttypes.TypeBind - } else { - spec.Type = mounttypes.TypeVolume - } - - spec.ReadOnly = !ReadWrite(mode) - - // cannot assume that if a volume driver is passed in that we should set it - if volumeDriver != "" && spec.Type == mounttypes.TypeVolume { - spec.VolumeOptions = &mounttypes.VolumeOptions{ - DriverConfig: &mounttypes.Driver{Name: volumeDriver}, - } - } - - if copyData, isSet := getCopyMode(mode); isSet { - if spec.VolumeOptions == nil { - spec.VolumeOptions = &mounttypes.VolumeOptions{} - } - spec.VolumeOptions.NoCopy = !copyData - } - if HasPropagation(mode) { - spec.BindOptions = &mounttypes.BindOptions{ - Propagation: GetPropagation(mode), - } - } - - mp, err := ParseMountSpec(spec, platformRawValidationOpts...) - if mp != nil { - mp.Mode = mode - } - if err != nil { - err = fmt.Errorf("%v: %v", errInvalidSpec(raw), err) - } - return mp, err -} - -// ParseMountSpec reads a mount config, validates it, and configures a mountpoint from it. -func ParseMountSpec(cfg mounttypes.Mount, options ...func(*validateOpts)) (*MountPoint, error) { - if err := validateMountConfig(&cfg, options...); err != nil { - return nil, err - } - mp := &MountPoint{ - RW: !cfg.ReadOnly, - Destination: clean(convertSlash(cfg.Target)), - Type: cfg.Type, - Spec: cfg, - } - - switch cfg.Type { - case mounttypes.TypeVolume: - if cfg.Source == "" { - mp.Name = stringid.GenerateNonCryptoID() - } else { - mp.Name = cfg.Source - } - mp.CopyData = DefaultCopyMode - - if cfg.VolumeOptions != nil { - if cfg.VolumeOptions.DriverConfig != nil { - mp.Driver = cfg.VolumeOptions.DriverConfig.Name - } - if cfg.VolumeOptions.NoCopy { - mp.CopyData = false - } - } - case mounttypes.TypeBind: - mp.Source = clean(convertSlash(cfg.Source)) - if cfg.BindOptions != nil && len(cfg.BindOptions.Propagation) > 0 { - mp.Propagation = cfg.BindOptions.Propagation - } else { - // If user did not specify a propagation mode, get - // default propagation mode. - mp.Propagation = DefaultPropagationMode - } - case mounttypes.TypeTmpfs: - // NOP - } - return mp, nil -} - -func errInvalidMode(mode string) error { - return fmt.Errorf("invalid mode: %v", mode) -} - -func errInvalidSpec(spec string) error { - return fmt.Errorf("invalid volume specification: '%s'", spec) -} diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume_copy.go b/components/cli/vendor/github.com/docker/docker/volume/volume_copy.go deleted file mode 100644 index 77f06a0d1f..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/volume_copy.go +++ /dev/null @@ -1,23 +0,0 @@ -package volume - -import "strings" - -// {=isEnabled} -var copyModes = map[string]bool{ - "nocopy": false, -} - -func copyModeExists(mode string) bool { - _, exists := copyModes[mode] - return exists -} - -// GetCopyMode gets the copy mode from the mode string for mounts -func getCopyMode(mode string) (bool, bool) { - for _, o := range strings.Split(mode, ",") { - if isEnabled, exists := copyModes[o]; exists { - return isEnabled, true - } - } - return DefaultCopyMode, false -} diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume_copy_unix.go b/components/cli/vendor/github.com/docker/docker/volume/volume_copy_unix.go deleted file mode 100644 index ad66e17637..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/volume_copy_unix.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build !windows - -package volume - -const ( - // DefaultCopyMode is the copy mode used by default for normal/named volumes - DefaultCopyMode = true -) diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume_copy_windows.go b/components/cli/vendor/github.com/docker/docker/volume/volume_copy_windows.go deleted file mode 100644 index 798638c878..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/volume_copy_windows.go +++ /dev/null @@ -1,6 +0,0 @@ -package volume - -const ( - // DefaultCopyMode is the copy mode used by default for normal/named volumes - DefaultCopyMode = false -) diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume_linux.go b/components/cli/vendor/github.com/docker/docker/volume/volume_linux.go deleted file mode 100644 index fdf7b63e4b..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/volume_linux.go +++ /dev/null @@ -1,56 +0,0 @@ -// +build linux - -package volume - -import ( - "fmt" - "strings" - - mounttypes "github.com/docker/docker/api/types/mount" -) - -// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string -// for mount(2). -func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions, readOnly bool) (string, error) { - var rawOpts []string - if readOnly { - rawOpts = append(rawOpts, "ro") - } - - if opt != nil && opt.Mode != 0 { - rawOpts = append(rawOpts, fmt.Sprintf("mode=%o", opt.Mode)) - } - - if opt != nil && opt.SizeBytes != 0 { - // calculate suffix here, making this linux specific, but that is - // okay, since API is that way anyways. - - // we do this by finding the suffix that divides evenly into the - // value, returning the value itself, with no suffix, if it fails. - // - // For the most part, we don't enforce any semantic to this values. - // The operating system will usually align this and enforce minimum - // and maximums. - var ( - size = opt.SizeBytes - suffix string - ) - for _, r := range []struct { - suffix string - divisor int64 - }{ - {"g", 1 << 30}, - {"m", 1 << 20}, - {"k", 1 << 10}, - } { - if size%r.divisor == 0 { - size = size / r.divisor - suffix = r.suffix - break - } - } - - rawOpts = append(rawOpts, fmt.Sprintf("size=%d%s", size, suffix)) - } - return strings.Join(rawOpts, ","), nil -} diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume_propagation_linux.go b/components/cli/vendor/github.com/docker/docker/volume/volume_propagation_linux.go deleted file mode 100644 index 1de57ab52b..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/volume_propagation_linux.go +++ /dev/null @@ -1,47 +0,0 @@ -// +build linux - -package volume - -import ( - "strings" - - mounttypes "github.com/docker/docker/api/types/mount" -) - -// DefaultPropagationMode defines what propagation mode should be used by -// default if user has not specified one explicitly. -// propagation modes -const DefaultPropagationMode = mounttypes.PropagationRPrivate - -var propagationModes = map[mounttypes.Propagation]bool{ - mounttypes.PropagationPrivate: true, - mounttypes.PropagationRPrivate: true, - mounttypes.PropagationSlave: true, - mounttypes.PropagationRSlave: true, - mounttypes.PropagationShared: true, - mounttypes.PropagationRShared: true, -} - -// GetPropagation extracts and returns the mount propagation mode. If there -// are no specifications, then by default it is "private". -func GetPropagation(mode string) mounttypes.Propagation { - for _, o := range strings.Split(mode, ",") { - prop := mounttypes.Propagation(o) - if propagationModes[prop] { - return prop - } - } - return DefaultPropagationMode -} - -// HasPropagation checks if there is a valid propagation mode present in -// passed string. Returns true if a valid propagation mode specifier is -// present, false otherwise. -func HasPropagation(mode string) bool { - for _, o := range strings.Split(mode, ",") { - if propagationModes[mounttypes.Propagation(o)] { - return true - } - } - return false -} diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume_propagation_unsupported.go b/components/cli/vendor/github.com/docker/docker/volume/volume_propagation_unsupported.go deleted file mode 100644 index 7311ffc2e0..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/volume_propagation_unsupported.go +++ /dev/null @@ -1,24 +0,0 @@ -// +build !linux - -package volume - -import mounttypes "github.com/docker/docker/api/types/mount" - -// DefaultPropagationMode is used only in linux. In other cases it returns -// empty string. -const DefaultPropagationMode mounttypes.Propagation = "" - -// propagation modes not supported on this platform. -var propagationModes = map[mounttypes.Propagation]bool{} - -// GetPropagation is not supported. Return empty string. -func GetPropagation(mode string) mounttypes.Propagation { - return DefaultPropagationMode -} - -// HasPropagation checks if there is a valid propagation mode present in -// passed string. Returns true if a valid propagation mode specifier is -// present, false otherwise. -func HasPropagation(mode string) bool { - return false -} diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume_unix.go b/components/cli/vendor/github.com/docker/docker/volume/volume_unix.go deleted file mode 100644 index e35b70c03b..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/volume_unix.go +++ /dev/null @@ -1,148 +0,0 @@ -// +build linux freebsd darwin solaris - -package volume - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - mounttypes "github.com/docker/docker/api/types/mount" -) - -var platformRawValidationOpts = []func(o *validateOpts){ - // need to make sure to not error out if the bind source does not exist on unix - // this is supported for historical reasons, the path will be automatically - // created later. - func(o *validateOpts) { o.skipBindSourceCheck = true }, -} - -// read-write modes -var rwModes = map[string]bool{ - "rw": true, - "ro": true, -} - -// label modes -var labelModes = map[string]bool{ - "Z": true, - "z": true, -} - -// consistency modes -var consistencyModes = map[mounttypes.Consistency]bool{ - mounttypes.ConsistencyFull: true, - mounttypes.ConsistencyCached: true, - mounttypes.ConsistencyDelegated: true, -} - -// BackwardsCompatible decides whether this mount point can be -// used in old versions of Docker or not. -// Only bind mounts and local volumes can be used in old versions of Docker. -func (m *MountPoint) BackwardsCompatible() bool { - return len(m.Source) > 0 || m.Driver == DefaultDriverName -} - -// HasResource checks whether the given absolute path for a container is in -// this mount point. If the relative path starts with `../` then the resource -// is outside of this mount point, but we can't simply check for this prefix -// because it misses `..` which is also outside of the mount, so check both. -func (m *MountPoint) HasResource(absolutePath string) bool { - relPath, err := filepath.Rel(m.Destination, absolutePath) - return err == nil && relPath != ".." && !strings.HasPrefix(relPath, fmt.Sprintf("..%c", filepath.Separator)) -} - -// IsVolumeNameValid checks a volume name in a platform specific manner. -func IsVolumeNameValid(name string) (bool, error) { - return true, nil -} - -// ValidMountMode will make sure the mount mode is valid. -// returns if it's a valid mount mode or not. -func ValidMountMode(mode string) bool { - if mode == "" { - return true - } - - rwModeCount := 0 - labelModeCount := 0 - propagationModeCount := 0 - copyModeCount := 0 - consistencyModeCount := 0 - - for _, o := range strings.Split(mode, ",") { - switch { - case rwModes[o]: - rwModeCount++ - case labelModes[o]: - labelModeCount++ - case propagationModes[mounttypes.Propagation(o)]: - propagationModeCount++ - case copyModeExists(o): - copyModeCount++ - case consistencyModes[mounttypes.Consistency(o)]: - consistencyModeCount++ - default: - return false - } - } - - // Only one string for each mode is allowed. - if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 || copyModeCount > 1 || consistencyModeCount > 1 { - return false - } - return true -} - -// ReadWrite tells you if a mode string is a valid read-write mode or not. -// If there are no specifications w.r.t read write mode, then by default -// it returns true. -func ReadWrite(mode string) bool { - if !ValidMountMode(mode) { - return false - } - - for _, o := range strings.Split(mode, ",") { - if o == "ro" { - return false - } - } - return true -} - -func validateNotRoot(p string) error { - p = filepath.Clean(convertSlash(p)) - if p == "/" { - return fmt.Errorf("invalid specification: destination can't be '/'") - } - return nil -} - -func validateCopyMode(mode bool) error { - return nil -} - -func convertSlash(p string) string { - return filepath.ToSlash(p) -} - -func splitRawSpec(raw string) ([]string, error) { - if strings.Count(raw, ":") > 2 { - return nil, errInvalidSpec(raw) - } - - arr := strings.SplitN(raw, ":", 3) - if arr[0] == "" { - return nil, errInvalidSpec(raw) - } - return arr, nil -} - -func clean(p string) string { - return filepath.Clean(p) -} - -func validateStat(fi os.FileInfo) error { - return nil -} diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume_unsupported.go b/components/cli/vendor/github.com/docker/docker/volume/volume_unsupported.go deleted file mode 100644 index ff9d6afa27..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/volume_unsupported.go +++ /dev/null @@ -1,16 +0,0 @@ -// +build !linux - -package volume - -import ( - "fmt" - "runtime" - - mounttypes "github.com/docker/docker/api/types/mount" -) - -// ConvertTmpfsOptions converts *mounttypes.TmpfsOptions to the raw option string -// for mount(2). -func ConvertTmpfsOptions(opt *mounttypes.TmpfsOptions, readOnly bool) (string, error) { - return "", fmt.Errorf("%s does not support tmpfs", runtime.GOOS) -} diff --git a/components/cli/vendor/github.com/docker/docker/volume/volume_windows.go b/components/cli/vendor/github.com/docker/docker/volume/volume_windows.go deleted file mode 100644 index 22f6fc7a14..0000000000 --- a/components/cli/vendor/github.com/docker/docker/volume/volume_windows.go +++ /dev/null @@ -1,201 +0,0 @@ -package volume - -import ( - "fmt" - "os" - "path/filepath" - "regexp" - "strings" -) - -// read-write modes -var rwModes = map[string]bool{ - "rw": true, -} - -// read-only modes -var roModes = map[string]bool{ - "ro": true, -} - -var platformRawValidationOpts = []func(*validateOpts){ - // filepath.IsAbs is weird on Windows: - // `c:` is not considered an absolute path - // `c:\` is considered an absolute path - // In any case, the regex matching below ensures absolute paths - // TODO: consider this a bug with filepath.IsAbs (?) - func(o *validateOpts) { o.skipAbsolutePathCheck = true }, -} - -const ( - // Spec should be in the format [source:]destination[:mode] - // - // Examples: c:\foo bar:d:rw - // c:\foo:d:\bar - // myname:d: - // d:\ - // - // Explanation of this regex! Thanks @thaJeztah on IRC and gist for help. See - // https://gist.github.com/thaJeztah/6185659e4978789fb2b2. A good place to - // test is https://regex-golang.appspot.com/assets/html/index.html - // - // Useful link for referencing named capturing groups: - // http://stackoverflow.com/questions/20750843/using-named-matches-from-go-regex - // - // There are three match groups: source, destination and mode. - // - - // RXHostDir is the first option of a source - RXHostDir = `[a-z]:\\(?:[^\\/:*?"<>|\r\n]+\\?)*` - // RXName is the second option of a source - RXName = `[^\\/:*?"<>|\r\n]+` - // RXReservedNames are reserved names not possible on Windows - RXReservedNames = `(con)|(prn)|(nul)|(aux)|(com[1-9])|(lpt[1-9])` - - // RXSource is the combined possibilities for a source - RXSource = `((?P((` + RXHostDir + `)|(` + RXName + `))):)?` - - // Source. Can be either a host directory, a name, or omitted: - // HostDir: - // - Essentially using the folder solution from - // https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9781449327453/ch08s18.html - // but adding case insensitivity. - // - Must be an absolute path such as c:\path - // - Can include spaces such as `c:\program files` - // - And then followed by a colon which is not in the capture group - // - And can be optional - // Name: - // - Must not contain invalid NTFS filename characters (https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx) - // - And then followed by a colon which is not in the capture group - // - And can be optional - - // RXDestination is the regex expression for the mount destination - RXDestination = `(?P([a-z]):((?:\\[^\\/:*?"<>\r\n]+)*\\?))` - // Destination (aka container path): - // - Variation on hostdir but can be a drive followed by colon as well - // - If a path, must be absolute. Can include spaces - // - Drive cannot be c: (explicitly checked in code, not RegEx) - - // RXMode is the regex expression for the mode of the mount - // Mode (optional): - // - Hopefully self explanatory in comparison to above regex's. - // - Colon is not in the capture group - RXMode = `(:(?P(?i)ro|rw))?` -) - -// BackwardsCompatible decides whether this mount point can be -// used in old versions of Docker or not. -// Windows volumes are never backwards compatible. -func (m *MountPoint) BackwardsCompatible() bool { - return false -} - -func splitRawSpec(raw string) ([]string, error) { - specExp := regexp.MustCompile(`^` + RXSource + RXDestination + RXMode + `$`) - match := specExp.FindStringSubmatch(strings.ToLower(raw)) - - // Must have something back - if len(match) == 0 { - return nil, errInvalidSpec(raw) - } - - var split []string - matchgroups := make(map[string]string) - // Pull out the sub expressions from the named capture groups - for i, name := range specExp.SubexpNames() { - matchgroups[name] = strings.ToLower(match[i]) - } - if source, exists := matchgroups["source"]; exists { - if source != "" { - split = append(split, source) - } - } - if destination, exists := matchgroups["destination"]; exists { - if destination != "" { - split = append(split, destination) - } - } - if mode, exists := matchgroups["mode"]; exists { - if mode != "" { - split = append(split, mode) - } - } - // Fix #26329. If the destination appears to be a file, and the source is null, - // it may be because we've fallen through the possible naming regex and hit a - // situation where the user intention was to map a file into a container through - // a local volume, but this is not supported by the platform. - if matchgroups["source"] == "" && matchgroups["destination"] != "" { - validName, err := IsVolumeNameValid(matchgroups["destination"]) - if err != nil { - return nil, err - } - if !validName { - if fi, err := os.Stat(matchgroups["destination"]); err == nil { - if !fi.IsDir() { - return nil, fmt.Errorf("file '%s' cannot be mapped. Only directories can be mapped on this platform", matchgroups["destination"]) - } - } - } - } - return split, nil -} - -// IsVolumeNameValid checks a volume name in a platform specific manner. -func IsVolumeNameValid(name string) (bool, error) { - nameExp := regexp.MustCompile(`^` + RXName + `$`) - if !nameExp.MatchString(name) { - return false, nil - } - nameExp = regexp.MustCompile(`^` + RXReservedNames + `$`) - if nameExp.MatchString(name) { - return false, fmt.Errorf("volume name %q cannot be a reserved word for Windows filenames", name) - } - return true, nil -} - -// ValidMountMode will make sure the mount mode is valid. -// returns if it's a valid mount mode or not. -func ValidMountMode(mode string) bool { - if mode == "" { - return true - } - return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)] -} - -// ReadWrite tells you if a mode string is a valid read-write mode or not. -func ReadWrite(mode string) bool { - return rwModes[strings.ToLower(mode)] || mode == "" -} - -func validateNotRoot(p string) error { - p = strings.ToLower(convertSlash(p)) - if p == "c:" || p == `c:\` { - return fmt.Errorf("destination path cannot be `c:` or `c:\\`: %v", p) - } - return nil -} - -func validateCopyMode(mode bool) error { - if mode { - return fmt.Errorf("Windows does not support copying image path content") - } - return nil -} - -func convertSlash(p string) string { - return filepath.FromSlash(p) -} - -func clean(p string) string { - if match, _ := regexp.MatchString("^[a-z]:$", p); match { - return p - } - return filepath.Clean(p) -} - -func validateStat(fi os.FileInfo) error { - if !fi.IsDir() { - return fmt.Errorf("source path must be a directory") - } - return nil -} diff --git a/components/cli/vendor/github.com/opencontainers/selinux/LICENSE b/components/cli/vendor/github.com/gotestyourself/gotestyourself/LICENSE similarity index 99% rename from components/cli/vendor/github.com/opencontainers/selinux/LICENSE rename to components/cli/vendor/github.com/gotestyourself/gotestyourself/LICENSE index 8dada3edaf..d645695673 100644 --- a/components/cli/vendor/github.com/opencontainers/selinux/LICENSE +++ b/components/cli/vendor/github.com/gotestyourself/gotestyourself/LICENSE @@ -1,3 +1,4 @@ + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -178,7 +179,7 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" + boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright {yyyy} {name of copyright owner} + Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/components/cli/vendor/github.com/gotestyourself/gotestyourself/README.md b/components/cli/vendor/github.com/gotestyourself/gotestyourself/README.md new file mode 100644 index 0000000000..2648074900 --- /dev/null +++ b/components/cli/vendor/github.com/gotestyourself/gotestyourself/README.md @@ -0,0 +1,32 @@ +# Go Test Yourself + +A collection of packages compatible with `go test` to support common testing +patterns. + +[![GoDoc](https://godoc.org/github.com/gotestyourself/gotestyourself?status.svg)](https://godoc.org/github.com/gotestyourself/gotestyourself) +[![CircleCI](https://circleci.com/gh/gotestyourself/gotestyourself/tree/master.svg?style=shield)](https://circleci.com/gh/gotestyourself/gotestyourself/tree/master) +[![Go Reportcard](https://goreportcard.com/badge/github.com/gotestyourself/gotestyourself)](https://goreportcard.com/report/github.com/gotestyourself/gotestyourself) + + +## Packages + +* [fs](http://godoc.org/github.com/gotestyourself/gotestyourself/fs) - + create test files and directories +* [golden](http://godoc.org/github.com/gotestyourself/gotestyourself/golden) - + compare large multi-line strings +* [testsum](http://godoc.org/github.com/gotestyourself/gotestyourself/testsum) - + a program to summarize `go test` output and test failures +* [icmd](http://godoc.org/github.com/gotestyourself/gotestyourself/icmd) - + execute binaries and test the output +* [skip](http://godoc.org/github.com/gotestyourself/gotestyourself/skip) - + skip tests based on conditions + + +## Related + +* [testify/assert](https://godoc.org/github.com/stretchr/testify/assert) and + [testify/require](https://godoc.org/github.com/stretchr/testify/require) - + assertion libraries with common assertions +* [golang/mock](https://github.com/golang/mock) - generate mocks for interfaces +* [testify/suite](https://godoc.org/github.com/stretchr/testify/suite) - + group test into suites to share common setup/teardown logic diff --git a/components/cli/vendor/github.com/gotestyourself/gotestyourself/fs/file.go b/components/cli/vendor/github.com/gotestyourself/gotestyourself/fs/file.go new file mode 100644 index 0000000000..dcda10a02a --- /dev/null +++ b/components/cli/vendor/github.com/gotestyourself/gotestyourself/fs/file.go @@ -0,0 +1,81 @@ +/*Package fs provides tools for creating and working with temporary files and +directories. +*/ +package fs + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/stretchr/testify/require" +) + +// Path objects return their filesystem path. Both File and Dir implement Path. +type Path interface { + Path() string +} + +// File is a temporary file on the filesystem +type File struct { + path string +} + +// NewFile creates a new file in a temporary directory using prefix as part of +// the filename. The PathOps are applied to the before returning the File. +func NewFile(t require.TestingT, prefix string, ops ...PathOp) *File { + tempfile, err := ioutil.TempFile("", prefix+"-") + require.NoError(t, err) + file := &File{path: tempfile.Name()} + require.NoError(t, tempfile.Close()) + + for _, op := range ops { + require.NoError(t, op(file)) + } + return file +} + +// Path returns the full path to the file +func (f *File) Path() string { + return f.path +} + +// Remove the file +func (f *File) Remove() { + // nolint: errcheck + os.Remove(f.path) +} + +// Dir is a temporary directory +type Dir struct { + path string +} + +// NewDir returns a new temporary directory using prefix as part of the directory +// name. The PathOps are applied before returning the Dir. +func NewDir(t require.TestingT, prefix string, ops ...PathOp) *Dir { + path, err := ioutil.TempDir("", prefix+"-") + require.NoError(t, err) + dir := &Dir{path: path} + + for _, op := range ops { + require.NoError(t, op(dir)) + } + return dir +} + +// Path returns the full path to the directory +func (d *Dir) Path() string { + return d.path +} + +// Remove the directory +func (d *Dir) Remove() { + // nolint: errcheck + os.RemoveAll(d.path) +} + +// Join returns a new path with this directory as the base of the path +func (d *Dir) Join(parts ...string) string { + return filepath.Join(append([]string{d.Path()}, parts...)...) +} diff --git a/components/cli/vendor/github.com/gotestyourself/gotestyourself/fs/ops.go b/components/cli/vendor/github.com/gotestyourself/gotestyourself/fs/ops.go new file mode 100644 index 0000000000..4fbc40f422 --- /dev/null +++ b/components/cli/vendor/github.com/gotestyourself/gotestyourself/fs/ops.go @@ -0,0 +1,94 @@ +package fs + +import ( + "io/ioutil" + "os" + "path/filepath" +) + +// PathOp is a function which accepts a Path to perform some operation +type PathOp func(path Path) error + +// WithContent writes content to a file at Path +func WithContent(content string) PathOp { + return func(path Path) error { + return ioutil.WriteFile(path.Path(), []byte(content), 0644) + } +} + +// WithBytes write bytes to a file at Path +func WithBytes(raw []byte) PathOp { + return func(path Path) error { + return ioutil.WriteFile(path.Path(), raw, 0644) + } +} + +// AsUser changes ownership of the file system object at Path +func AsUser(uid, gid int) PathOp { + return func(path Path) error { + return os.Chown(path.Path(), uid, gid) + } +} + +// WithFile creates a file in the directory at path with content +func WithFile(filename, content string) PathOp { + return func(path Path) error { + return createFile(path.Path(), filename, content) + } +} + +func createFile(dir, filename, content string) error { + fullpath := filepath.Join(dir, filepath.FromSlash(filename)) + return ioutil.WriteFile(fullpath, []byte(content), 0644) +} + +// WithFiles creates all the files in the directory at path with their content +func WithFiles(files map[string]string) PathOp { + return func(path Path) error { + for filename, content := range files { + if err := createFile(path.Path(), filename, content); err != nil { + return err + } + } + return nil + } +} + +// FromDir copies the directory tree from the source path into the new Dir +func FromDir(source string) PathOp { + return func(path Path) error { + return copyDirectory(source, path.Path()) + } +} + +func copyDirectory(source, dest string) error { + entries, err := ioutil.ReadDir(source) + if err != nil { + return err + } + for _, entry := range entries { + sourcePath := filepath.Join(source, entry.Name()) + destPath := filepath.Join(dest, entry.Name()) + if entry.IsDir() { + if err := os.Mkdir(destPath, 0755); err != nil { + return err + } + if err := copyDirectory(sourcePath, destPath); err != nil { + return err + } + continue + } + if err := copyFile(sourcePath, destPath); err != nil { + return err + } + } + return nil +} + +func copyFile(source, dest string) error { + content, err := ioutil.ReadFile(source) + if err != nil { + return err + } + return ioutil.WriteFile(dest, content, 0644) +} diff --git a/components/cli/vendor/github.com/gotestyourself/gotestyourself/golden/golden.go b/components/cli/vendor/github.com/gotestyourself/gotestyourself/golden/golden.go new file mode 100644 index 0000000000..230ff685cd --- /dev/null +++ b/components/cli/vendor/github.com/gotestyourself/gotestyourself/golden/golden.go @@ -0,0 +1,71 @@ +/*Package golden provides tools for comparing large mutli-line strings. + +Golden files are files in the ./testdata/ subdirectory of the package under test. +*/ +package golden + +import ( + "flag" + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/pmezard/go-difflib/difflib" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var flagUpdate = flag.Bool("test.update-golden", false, "update golden file") + +// Get returns the golden file content +func Get(t require.TestingT, filename string) []byte { + expected, err := ioutil.ReadFile(Path(filename)) + require.NoError(t, err) + return expected +} + +// Path returns the full path to a golden file +func Path(filename string) string { + return filepath.Join("testdata", filename) +} + +func update(t require.TestingT, filename string, actual []byte) { + if *flagUpdate { + err := ioutil.WriteFile(Path(filename), actual, 0644) + require.NoError(t, err) + } +} + +// Assert compares the actual content to the expected content in the golden file. +// If `--update-golden` is set then the actual content is written to the golden +// file. +// Returns whether the assertion was successful (true) or not (false) +func Assert(t require.TestingT, actual string, filename string, msgAndArgs ...interface{}) bool { + expected := Get(t, filename) + update(t, filename, []byte(actual)) + + if assert.ObjectsAreEqual(expected, []byte(actual)) { + return true + } + + diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ + A: difflib.SplitLines(string(expected)), + B: difflib.SplitLines(actual), + FromFile: "Expected", + ToFile: "Actual", + Context: 3, + }) + require.NoError(t, err, msgAndArgs...) + return assert.Fail(t, fmt.Sprintf("Not Equal: \n%s", diff), msgAndArgs...) +} + +// AssertBytes compares the actual result to the expected result in the golden +// file. If `--update-golden` is set then the actual content is written to the +// golden file. +// Returns whether the assertion was successful (true) or not (false) +// nolint: lll +func AssertBytes(t require.TestingT, actual []byte, filename string, msgAndArgs ...interface{}) bool { + expected := Get(t, filename) + update(t, filename, actual) + return assert.Equal(t, expected, actual, msgAndArgs...) +} diff --git a/components/cli/vendor/github.com/gotestyourself/gotestyourself/icmd/command.go b/components/cli/vendor/github.com/gotestyourself/gotestyourself/icmd/command.go new file mode 100644 index 0000000000..2470fdc662 --- /dev/null +++ b/components/cli/vendor/github.com/gotestyourself/gotestyourself/icmd/command.go @@ -0,0 +1,277 @@ +/*Package icmd executes binaries and provides convenient assertions for testing the results. + */ +package icmd + +import ( + "bytes" + "fmt" + "io" + "os/exec" + "path/filepath" + "runtime" + "strings" + "sync" + "time" +) + +type testingT interface { + Fatalf(string, ...interface{}) +} + +const ( + // None is a token to inform Result.Assert that the output should be empty + None string = "" +) + +type lockedBuffer struct { + m sync.RWMutex + buf bytes.Buffer +} + +func (buf *lockedBuffer) Write(b []byte) (int, error) { + buf.m.Lock() + defer buf.m.Unlock() + return buf.buf.Write(b) +} + +func (buf *lockedBuffer) String() string { + buf.m.RLock() + defer buf.m.RUnlock() + return buf.buf.String() +} + +// Result stores the result of running a command +type Result struct { + Cmd *exec.Cmd + ExitCode int + Error error + // Timeout is true if the command was killed because it ran for too long + Timeout bool + outBuffer *lockedBuffer + errBuffer *lockedBuffer +} + +// Assert compares the Result against the Expected struct, and fails the test if +// any of the expectations are not met. +func (r *Result) Assert(t testingT, exp Expected) *Result { + err := r.Compare(exp) + if err == nil { + return r + } + _, file, line, ok := runtime.Caller(1) + if ok { + t.Fatalf("at %s:%d - %s\n", filepath.Base(file), line, err.Error()) + } else { + t.Fatalf("(no file/line info) - %s", err.Error()) + } + return nil +} + +// Compare returns a formatted error with the command, stdout, stderr, exit +// code, and any failed expectations +// nolint: gocyclo +func (r *Result) Compare(exp Expected) error { + errors := []string{} + add := func(format string, args ...interface{}) { + errors = append(errors, fmt.Sprintf(format, args...)) + } + + if exp.ExitCode != r.ExitCode { + add("ExitCode was %d expected %d", r.ExitCode, exp.ExitCode) + } + if exp.Timeout != r.Timeout { + if exp.Timeout { + add("Expected command to timeout") + } else { + add("Expected command to finish, but it hit the timeout") + } + } + if !matchOutput(exp.Out, r.Stdout()) { + add("Expected stdout to contain %q", exp.Out) + } + if !matchOutput(exp.Err, r.Stderr()) { + add("Expected stderr to contain %q", exp.Err) + } + switch { + // If a non-zero exit code is expected there is going to be an error. + // Don't require an error message as well as an exit code because the + // error message is going to be "exit status which is not useful + case exp.Error == "" && exp.ExitCode != 0: + case exp.Error == "" && r.Error != nil: + add("Expected no error") + case exp.Error != "" && r.Error == nil: + add("Expected error to contain %q, but there was no error", exp.Error) + case exp.Error != "" && !strings.Contains(r.Error.Error(), exp.Error): + add("Expected error to contain %q", exp.Error) + } + + if len(errors) == 0 { + return nil + } + return fmt.Errorf("%s\nFailures:\n%s", r, strings.Join(errors, "\n")) +} + +func matchOutput(expected string, actual string) bool { + switch expected { + case None: + return actual == "" + default: + return strings.Contains(actual, expected) + } +} + +func (r *Result) String() string { + var timeout string + if r.Timeout { + timeout = " (timeout)" + } + + return fmt.Sprintf(` +Command: %s +ExitCode: %d%s +Error: %v +Stdout: %v +Stderr: %v +`, + strings.Join(r.Cmd.Args, " "), + r.ExitCode, + timeout, + r.Error, + r.Stdout(), + r.Stderr()) +} + +// Expected is the expected output from a Command. This struct is compared to a +// Result struct by Result.Assert(). +type Expected struct { + ExitCode int + Timeout bool + Error string + Out string + Err string +} + +// Success is the default expected result. A Success result is one with a 0 +// ExitCode. +var Success = Expected{} + +// Stdout returns the stdout of the process as a string +func (r *Result) Stdout() string { + return r.outBuffer.String() +} + +// Stderr returns the stderr of the process as a string +func (r *Result) Stderr() string { + return r.errBuffer.String() +} + +// Combined returns the stdout and stderr combined into a single string +func (r *Result) Combined() string { + return r.outBuffer.String() + r.errBuffer.String() +} + +// SetExitError sets Error and ExitCode based on Error +func (r *Result) SetExitError(err error) { + if err == nil { + return + } + r.Error = err + r.ExitCode = processExitCode(err) +} + +// Cmd contains the arguments and options for a process to run as part of a test +// suite. +type Cmd struct { + Command []string + Timeout time.Duration + Stdin io.Reader + Stdout io.Writer + Dir string + Env []string +} + +// Command create a simple Cmd with the specified command and arguments +func Command(command string, args ...string) Cmd { + return Cmd{Command: append([]string{command}, args...)} +} + +// RunCmd runs a command and returns a Result +func RunCmd(cmd Cmd, cmdOperators ...func(*Cmd)) *Result { + for _, op := range cmdOperators { + op(&cmd) + } + result := StartCmd(cmd) + if result.Error != nil { + return result + } + return WaitOnCmd(cmd.Timeout, result) +} + +// RunCommand parses a command line and runs it, returning a result +func RunCommand(command string, args ...string) *Result { + return RunCmd(Command(command, args...)) +} + +// StartCmd starts a command, but doesn't wait for it to finish +func StartCmd(cmd Cmd) *Result { + result := buildCmd(cmd) + if result.Error != nil { + return result + } + result.SetExitError(result.Cmd.Start()) + return result +} + +func buildCmd(cmd Cmd) *Result { + var execCmd *exec.Cmd + switch len(cmd.Command) { + case 1: + execCmd = exec.Command(cmd.Command[0]) + default: + execCmd = exec.Command(cmd.Command[0], cmd.Command[1:]...) + } + outBuffer := new(lockedBuffer) + errBuffer := new(lockedBuffer) + + execCmd.Stdin = cmd.Stdin + execCmd.Dir = cmd.Dir + execCmd.Env = cmd.Env + if cmd.Stdout != nil { + execCmd.Stdout = io.MultiWriter(outBuffer, cmd.Stdout) + } else { + execCmd.Stdout = outBuffer + } + execCmd.Stderr = errBuffer + return &Result{ + Cmd: execCmd, + outBuffer: outBuffer, + errBuffer: errBuffer, + } +} + +// WaitOnCmd waits for a command to complete. If timeout is non-nil then +// only wait until the timeout. +func WaitOnCmd(timeout time.Duration, result *Result) *Result { + if timeout == time.Duration(0) { + result.SetExitError(result.Cmd.Wait()) + return result + } + + done := make(chan error, 1) + // Wait for command to exit in a goroutine + go func() { + done <- result.Cmd.Wait() + }() + + select { + case <-time.After(timeout): + killErr := result.Cmd.Process.Kill() + if killErr != nil { + fmt.Printf("failed to kill (pid=%d): %v\n", result.Cmd.Process.Pid, killErr) + } + result.Timeout = true + case err := <-done: + result.SetExitError(err) + } + return result +} diff --git a/components/cli/vendor/github.com/gotestyourself/gotestyourself/icmd/exitcode.go b/components/cli/vendor/github.com/gotestyourself/gotestyourself/icmd/exitcode.go new file mode 100644 index 0000000000..32272b4bbb --- /dev/null +++ b/components/cli/vendor/github.com/gotestyourself/gotestyourself/icmd/exitcode.go @@ -0,0 +1,32 @@ +package icmd + +import ( + "os/exec" + "syscall" + + "github.com/pkg/errors" +) + +// GetExitCode returns the ExitStatus of a process from the error returned by +// exec.Run(). If the exit status could not be parsed an error is returned. +func GetExitCode(err error) (int, error) { + if exiterr, ok := err.(*exec.ExitError); ok { + if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok { + return procExit.ExitStatus(), nil + } + } + return 0, errors.Wrap(err, "failed to get exit code") +} + +func processExitCode(err error) (exitCode int) { + if err == nil { + return 0 + } + exitCode, exiterr := GetExitCode(err) + if exiterr != nil { + // TODO: Fix this so we check the error's text. + // we've failed to retrieve exit code, so we set it to 127 + return 127 + } + return exitCode +} diff --git a/components/cli/vendor/github.com/gotestyourself/gotestyourself/skip/skip.go b/components/cli/vendor/github.com/gotestyourself/gotestyourself/skip/skip.go new file mode 100644 index 0000000000..d92110b0ef --- /dev/null +++ b/components/cli/vendor/github.com/gotestyourself/gotestyourself/skip/skip.go @@ -0,0 +1,133 @@ +/*Package skip provides functions for skipping based on a condition. + */ +package skip + +import ( + "bytes" + "fmt" + "go/ast" + "go/format" + "go/parser" + "go/token" + "io/ioutil" + "path" + "reflect" + "runtime" + "strings" + + "github.com/pkg/errors" +) + +type skipT interface { + Skip(args ...interface{}) + Log(args ...interface{}) +} + +// If skips the test if the check function returns true. The skip message will +// contain the name of the check function. Extra message text can be passed as a +// format string with args +func If(t skipT, check func() bool, msgAndArgs ...interface{}) { + if check() { + t.Skip(formatWithCustomMessage( + getFunctionName(check), + formatMessage(msgAndArgs...))) + } +} + +func getFunctionName(function func() bool) string { + funcPath := runtime.FuncForPC(reflect.ValueOf(function).Pointer()).Name() + return strings.SplitN(path.Base(funcPath), ".", 2)[1] +} + +// IfCondition skips the test if the condition is true. The skip message will +// contain the source of the expression passed as the condition. Extra message +// text can be passed as a format string with args. +func IfCondition(t skipT, condition bool, msgAndArgs ...interface{}) { + if !condition { + return + } + source, err := getConditionSource() + if err != nil { + t.Log(err.Error()) + t.Skip(formatMessage(msgAndArgs...)) + } + t.Skip(formatWithCustomMessage(source, formatMessage(msgAndArgs...))) +} + +func getConditionSource() (string, error) { + const callstackIndex = 3 + lines, err := getSourceLine(callstackIndex) + if err != nil { + return "", err + } + + for i := range lines { + source := strings.Join(lines[len(lines)-i-1:], "\n") + node, err := parser.ParseExpr(source) + if err == nil { + return getConditionArgFromAST(node) + } + } + return "", errors.Wrapf(err, "failed to parse source") +} + +// maxContextLines is the maximum number of lines to scan for a complete +// skip.If() statement +const maxContextLines = 10 + +// getSourceLines returns the source line which called skip.If() along with a +// few preceding lines. To properly parse the AST a complete statement is +// required, and that statement may be split across multiple lines, so include +// up to maxContextLines. +func getSourceLine(stackIndex int) ([]string, error) { + _, filename, line, ok := runtime.Caller(stackIndex) + if !ok { + return nil, errors.New("failed to get caller info") + } + + raw, err := ioutil.ReadFile(filename) + if err != nil { + return nil, errors.Wrapf(err, "failed to read source file: %s", filename) + } + + lines := strings.Split(string(raw), "\n") + if len(lines) < line { + return nil, errors.Errorf("file %s does not have line %d", filename, line) + } + firstLine := line - maxContextLines + if firstLine < 0 { + firstLine = 0 + } + return lines[firstLine:line], nil +} + +func getConditionArgFromAST(node ast.Expr) (string, error) { + switch expr := node.(type) { + case *ast.CallExpr: + buf := new(bytes.Buffer) + err := format.Node(buf, token.NewFileSet(), expr.Args[1]) + return buf.String(), err + } + return "", errors.New("unexpected ast") +} + +func formatMessage(msgAndArgs ...interface{}) string { + switch len(msgAndArgs) { + case 0: + return "" + case 1: + return msgAndArgs[0].(string) + default: + return fmt.Sprintf(msgAndArgs[0].(string), msgAndArgs[1:]...) + } +} + +func formatWithCustomMessage(source, custom string) string { + switch { + case custom == "": + return source + case source == "": + return custom + } + return fmt.Sprintf("%s: %s", source, custom) +} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go deleted file mode 100644 index 35fc8eb961..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups.go +++ /dev/null @@ -1,64 +0,0 @@ -// +build linux - -package cgroups - -import ( - "fmt" - - "github.com/opencontainers/runc/libcontainer/configs" -) - -type Manager interface { - // Applies cgroup configuration to the process with the specified pid - Apply(pid int) error - - // Returns the PIDs inside the cgroup set - GetPids() ([]int, error) - - // Returns the PIDs inside the cgroup set & all sub-cgroups - GetAllPids() ([]int, error) - - // Returns statistics for the cgroup set - GetStats() (*Stats, error) - - // Toggles the freezer cgroup according with specified state - Freeze(state configs.FreezerState) error - - // Destroys the cgroup set - Destroy() error - - // NewCgroupManager() and LoadCgroupManager() require following attributes: - // Paths map[string]string - // Cgroups *cgroups.Cgroup - // Paths maps cgroup subsystem to path at which it is mounted. - // Cgroups specifies specific cgroup settings for the various subsystems - - // Returns cgroup paths to save in a state file and to be able to - // restore the object later. - GetPaths() map[string]string - - // Sets the cgroup as configured. - Set(container *configs.Config) error -} - -type NotFoundError struct { - Subsystem string -} - -func (e *NotFoundError) Error() string { - return fmt.Sprintf("mountpoint for %s not found", e.Subsystem) -} - -func NewNotFoundError(sub string) error { - return &NotFoundError{ - Subsystem: sub, - } -} - -func IsNotFound(err error) bool { - if err == nil { - return false - } - _, ok := err.(*NotFoundError) - return ok -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups_unsupported.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups_unsupported.go deleted file mode 100644 index 278d507e28..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/cgroups_unsupported.go +++ /dev/null @@ -1,3 +0,0 @@ -// +build !linux - -package cgroups diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go deleted file mode 100644 index b483f1bf98..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/stats.go +++ /dev/null @@ -1,106 +0,0 @@ -// +build linux - -package cgroups - -type ThrottlingData struct { - // Number of periods with throttling active - Periods uint64 `json:"periods,omitempty"` - // Number of periods when the container hit its throttling limit. - ThrottledPeriods uint64 `json:"throttled_periods,omitempty"` - // Aggregate time the container was throttled for in nanoseconds. - ThrottledTime uint64 `json:"throttled_time,omitempty"` -} - -// CpuUsage denotes the usage of a CPU. -// All CPU stats are aggregate since container inception. -type CpuUsage struct { - // Total CPU time consumed. - // Units: nanoseconds. - TotalUsage uint64 `json:"total_usage,omitempty"` - // Total CPU time consumed per core. - // Units: nanoseconds. - PercpuUsage []uint64 `json:"percpu_usage,omitempty"` - // Time spent by tasks of the cgroup in kernel mode. - // Units: nanoseconds. - UsageInKernelmode uint64 `json:"usage_in_kernelmode"` - // Time spent by tasks of the cgroup in user mode. - // Units: nanoseconds. - UsageInUsermode uint64 `json:"usage_in_usermode"` -} - -type CpuStats struct { - CpuUsage CpuUsage `json:"cpu_usage,omitempty"` - ThrottlingData ThrottlingData `json:"throttling_data,omitempty"` -} - -type MemoryData struct { - Usage uint64 `json:"usage,omitempty"` - MaxUsage uint64 `json:"max_usage,omitempty"` - Failcnt uint64 `json:"failcnt"` - Limit uint64 `json:"limit"` -} - -type MemoryStats struct { - // memory used for cache - Cache uint64 `json:"cache,omitempty"` - // usage of memory - Usage MemoryData `json:"usage,omitempty"` - // usage of memory + swap - SwapUsage MemoryData `json:"swap_usage,omitempty"` - // usage of kernel memory - KernelUsage MemoryData `json:"kernel_usage,omitempty"` - // usage of kernel TCP memory - KernelTCPUsage MemoryData `json:"kernel_tcp_usage,omitempty"` - - Stats map[string]uint64 `json:"stats,omitempty"` -} - -type PidsStats struct { - // number of pids in the cgroup - Current uint64 `json:"current,omitempty"` - // active pids hard limit - Limit uint64 `json:"limit,omitempty"` -} - -type BlkioStatEntry struct { - Major uint64 `json:"major,omitempty"` - Minor uint64 `json:"minor,omitempty"` - Op string `json:"op,omitempty"` - Value uint64 `json:"value,omitempty"` -} - -type BlkioStats struct { - // number of bytes tranferred to and from the block device - IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"` - IoServicedRecursive []BlkioStatEntry `json:"io_serviced_recursive,omitempty"` - IoQueuedRecursive []BlkioStatEntry `json:"io_queue_recursive,omitempty"` - IoServiceTimeRecursive []BlkioStatEntry `json:"io_service_time_recursive,omitempty"` - IoWaitTimeRecursive []BlkioStatEntry `json:"io_wait_time_recursive,omitempty"` - IoMergedRecursive []BlkioStatEntry `json:"io_merged_recursive,omitempty"` - IoTimeRecursive []BlkioStatEntry `json:"io_time_recursive,omitempty"` - SectorsRecursive []BlkioStatEntry `json:"sectors_recursive,omitempty"` -} - -type HugetlbStats struct { - // current res_counter usage for hugetlb - Usage uint64 `json:"usage,omitempty"` - // maximum usage ever recorded. - MaxUsage uint64 `json:"max_usage,omitempty"` - // number of times hugetlb usage allocation failure. - Failcnt uint64 `json:"failcnt"` -} - -type Stats struct { - CpuStats CpuStats `json:"cpu_stats,omitempty"` - MemoryStats MemoryStats `json:"memory_stats,omitempty"` - PidsStats PidsStats `json:"pids_stats,omitempty"` - BlkioStats BlkioStats `json:"blkio_stats,omitempty"` - // the map is in the format "size of hugepage: stats of the hugepage" - HugetlbStats map[string]HugetlbStats `json:"hugetlb_stats,omitempty"` -} - -func NewStats() *Stats { - memoryStats := MemoryStats{Stats: make(map[string]uint64)} - hugetlbStats := make(map[string]HugetlbStats) - return &Stats{MemoryStats: memoryStats, HugetlbStats: hugetlbStats} -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go deleted file mode 100644 index c6db0039e6..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/cgroups/utils.go +++ /dev/null @@ -1,436 +0,0 @@ -// +build linux - -package cgroups - -import ( - "bufio" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/docker/go-units" -) - -const ( - cgroupNamePrefix = "name=" - CgroupProcesses = "cgroup.procs" -) - -// https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt -func FindCgroupMountpoint(subsystem string) (string, error) { - // We are not using mount.GetMounts() because it's super-inefficient, - // parsing it directly sped up x10 times because of not using Sscanf. - // It was one of two major performance drawbacks in container start. - if !isSubsystemAvailable(subsystem) { - return "", NewNotFoundError(subsystem) - } - f, err := os.Open("/proc/self/mountinfo") - if err != nil { - return "", err - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - txt := scanner.Text() - fields := strings.Split(txt, " ") - for _, opt := range strings.Split(fields[len(fields)-1], ",") { - if opt == subsystem { - return fields[4], nil - } - } - } - if err := scanner.Err(); err != nil { - return "", err - } - - return "", NewNotFoundError(subsystem) -} - -func FindCgroupMountpointAndRoot(subsystem string) (string, string, error) { - if !isSubsystemAvailable(subsystem) { - return "", "", NewNotFoundError(subsystem) - } - f, err := os.Open("/proc/self/mountinfo") - if err != nil { - return "", "", err - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - txt := scanner.Text() - fields := strings.Split(txt, " ") - for _, opt := range strings.Split(fields[len(fields)-1], ",") { - if opt == subsystem { - return fields[4], fields[3], nil - } - } - } - if err := scanner.Err(); err != nil { - return "", "", err - } - - return "", "", NewNotFoundError(subsystem) -} - -func isSubsystemAvailable(subsystem string) bool { - cgroups, err := ParseCgroupFile("/proc/self/cgroup") - if err != nil { - return false - } - _, avail := cgroups[subsystem] - return avail -} - -func FindCgroupMountpointDir() (string, error) { - f, err := os.Open("/proc/self/mountinfo") - if err != nil { - return "", err - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - text := scanner.Text() - fields := strings.Split(text, " ") - // Safe as mountinfo encodes mountpoints with spaces as \040. - index := strings.Index(text, " - ") - postSeparatorFields := strings.Fields(text[index+3:]) - numPostFields := len(postSeparatorFields) - - // This is an error as we can't detect if the mount is for "cgroup" - if numPostFields == 0 { - return "", fmt.Errorf("Found no fields post '-' in %q", text) - } - - if postSeparatorFields[0] == "cgroup" { - // Check that the mount is properly formated. - if numPostFields < 3 { - return "", fmt.Errorf("Error found less than 3 fields post '-' in %q", text) - } - - return filepath.Dir(fields[4]), nil - } - } - if err := scanner.Err(); err != nil { - return "", err - } - - return "", NewNotFoundError("cgroup") -} - -type Mount struct { - Mountpoint string - Root string - Subsystems []string -} - -func (m Mount) GetThisCgroupDir(cgroups map[string]string) (string, error) { - if len(m.Subsystems) == 0 { - return "", fmt.Errorf("no subsystem for mount") - } - - return getControllerPath(m.Subsystems[0], cgroups) -} - -func getCgroupMountsHelper(ss map[string]bool, mi io.Reader, all bool) ([]Mount, error) { - res := make([]Mount, 0, len(ss)) - scanner := bufio.NewScanner(mi) - numFound := 0 - for scanner.Scan() && numFound < len(ss) { - txt := scanner.Text() - sepIdx := strings.Index(txt, " - ") - if sepIdx == -1 { - return nil, fmt.Errorf("invalid mountinfo format") - } - if txt[sepIdx+3:sepIdx+10] == "cgroup2" || txt[sepIdx+3:sepIdx+9] != "cgroup" { - continue - } - fields := strings.Split(txt, " ") - m := Mount{ - Mountpoint: fields[4], - Root: fields[3], - } - for _, opt := range strings.Split(fields[len(fields)-1], ",") { - if !ss[opt] { - continue - } - if strings.HasPrefix(opt, cgroupNamePrefix) { - m.Subsystems = append(m.Subsystems, opt[len(cgroupNamePrefix):]) - } else { - m.Subsystems = append(m.Subsystems, opt) - } - if !all { - numFound++ - } - } - res = append(res, m) - } - if err := scanner.Err(); err != nil { - return nil, err - } - return res, nil -} - -// GetCgroupMounts returns the mounts for the cgroup subsystems. -// all indicates whether to return just the first instance or all the mounts. -func GetCgroupMounts(all bool) ([]Mount, error) { - f, err := os.Open("/proc/self/mountinfo") - if err != nil { - return nil, err - } - defer f.Close() - - allSubsystems, err := ParseCgroupFile("/proc/self/cgroup") - if err != nil { - return nil, err - } - - allMap := make(map[string]bool) - for s := range allSubsystems { - allMap[s] = true - } - return getCgroupMountsHelper(allMap, f, all) -} - -// GetAllSubsystems returns all the cgroup subsystems supported by the kernel -func GetAllSubsystems() ([]string, error) { - f, err := os.Open("/proc/cgroups") - if err != nil { - return nil, err - } - defer f.Close() - - subsystems := []string{} - - s := bufio.NewScanner(f) - for s.Scan() { - if err := s.Err(); err != nil { - return nil, err - } - text := s.Text() - if text[0] != '#' { - parts := strings.Fields(text) - if len(parts) >= 4 && parts[3] != "0" { - subsystems = append(subsystems, parts[0]) - } - } - } - return subsystems, nil -} - -// GetThisCgroupDir returns the relative path to the cgroup docker is running in. -func GetThisCgroupDir(subsystem string) (string, error) { - cgroups, err := ParseCgroupFile("/proc/self/cgroup") - if err != nil { - return "", err - } - - return getControllerPath(subsystem, cgroups) -} - -func GetInitCgroupDir(subsystem string) (string, error) { - - cgroups, err := ParseCgroupFile("/proc/1/cgroup") - if err != nil { - return "", err - } - - return getControllerPath(subsystem, cgroups) -} - -func readProcsFile(dir string) ([]int, error) { - f, err := os.Open(filepath.Join(dir, CgroupProcesses)) - if err != nil { - return nil, err - } - defer f.Close() - - var ( - s = bufio.NewScanner(f) - out = []int{} - ) - - for s.Scan() { - if t := s.Text(); t != "" { - pid, err := strconv.Atoi(t) - if err != nil { - return nil, err - } - out = append(out, pid) - } - } - return out, nil -} - -// ParseCgroupFile parses the given cgroup file, typically from -// /proc//cgroup, into a map of subgroups to cgroup names. -func ParseCgroupFile(path string) (map[string]string, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() - - return parseCgroupFromReader(f) -} - -// helper function for ParseCgroupFile to make testing easier -func parseCgroupFromReader(r io.Reader) (map[string]string, error) { - s := bufio.NewScanner(r) - cgroups := make(map[string]string) - - for s.Scan() { - if err := s.Err(); err != nil { - return nil, err - } - - text := s.Text() - // from cgroups(7): - // /proc/[pid]/cgroup - // ... - // For each cgroup hierarchy ... there is one entry - // containing three colon-separated fields of the form: - // hierarchy-ID:subsystem-list:cgroup-path - parts := strings.SplitN(text, ":", 3) - if len(parts) < 3 { - return nil, fmt.Errorf("invalid cgroup entry: must contain at least two colons: %v", text) - } - - for _, subs := range strings.Split(parts[1], ",") { - cgroups[subs] = parts[2] - } - } - return cgroups, nil -} - -func getControllerPath(subsystem string, cgroups map[string]string) (string, error) { - - if p, ok := cgroups[subsystem]; ok { - return p, nil - } - - if p, ok := cgroups[cgroupNamePrefix+subsystem]; ok { - return p, nil - } - - return "", NewNotFoundError(subsystem) -} - -func PathExists(path string) bool { - if _, err := os.Stat(path); err != nil { - return false - } - return true -} - -func EnterPid(cgroupPaths map[string]string, pid int) error { - for _, path := range cgroupPaths { - if PathExists(path) { - if err := WriteCgroupProc(path, pid); err != nil { - return err - } - } - } - return nil -} - -// RemovePaths iterates over the provided paths removing them. -// We trying to remove all paths five times with increasing delay between tries. -// If after all there are not removed cgroups - appropriate error will be -// returned. -func RemovePaths(paths map[string]string) (err error) { - delay := 10 * time.Millisecond - for i := 0; i < 5; i++ { - if i != 0 { - time.Sleep(delay) - delay *= 2 - } - for s, p := range paths { - os.RemoveAll(p) - // TODO: here probably should be logging - _, err := os.Stat(p) - // We need this strange way of checking cgroups existence because - // RemoveAll almost always returns error, even on already removed - // cgroups - if os.IsNotExist(err) { - delete(paths, s) - } - } - if len(paths) == 0 { - return nil - } - } - return fmt.Errorf("Failed to remove paths: %v", paths) -} - -func GetHugePageSize() ([]string, error) { - var pageSizes []string - sizeList := []string{"B", "kB", "MB", "GB", "TB", "PB"} - files, err := ioutil.ReadDir("/sys/kernel/mm/hugepages") - if err != nil { - return pageSizes, err - } - for _, st := range files { - nameArray := strings.Split(st.Name(), "-") - pageSize, err := units.RAMInBytes(nameArray[1]) - if err != nil { - return []string{}, err - } - sizeString := units.CustomSize("%g%s", float64(pageSize), 1024.0, sizeList) - pageSizes = append(pageSizes, sizeString) - } - - return pageSizes, nil -} - -// GetPids returns all pids, that were added to cgroup at path. -func GetPids(path string) ([]int, error) { - return readProcsFile(path) -} - -// GetAllPids returns all pids, that were added to cgroup at path and to all its -// subcgroups. -func GetAllPids(path string) ([]int, error) { - var pids []int - // collect pids from all sub-cgroups - err := filepath.Walk(path, func(p string, info os.FileInfo, iErr error) error { - dir, file := filepath.Split(p) - if file != CgroupProcesses { - return nil - } - if iErr != nil { - return iErr - } - cPids, err := readProcsFile(dir) - if err != nil { - return err - } - pids = append(pids, cPids...) - return nil - }) - return pids, err -} - -// WriteCgroupProc writes the specified pid into the cgroup's cgroup.procs file -func WriteCgroupProc(dir string, pid int) error { - // Normally dir should not be empty, one case is that cgroup subsystem - // is not mounted, we will get empty dir, and we want it fail here. - if dir == "" { - return fmt.Errorf("no such directory for %s", CgroupProcesses) - } - - // Dont attach any pid to the cgroup if -1 is specified as a pid - if pid != -1 { - if err := ioutil.WriteFile(filepath.Join(dir, CgroupProcesses), []byte(strconv.Itoa(pid)), 0700); err != nil { - return fmt.Errorf("failed to write %v to %v: %v", pid, CgroupProcesses, err) - } - } - return nil -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go deleted file mode 100644 index e0f3ca1653..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/blkio_device.go +++ /dev/null @@ -1,61 +0,0 @@ -package configs - -import "fmt" - -// blockIODevice holds major:minor format supported in blkio cgroup -type blockIODevice struct { - // Major is the device's major number - Major int64 `json:"major"` - // Minor is the device's minor number - Minor int64 `json:"minor"` -} - -// WeightDevice struct holds a `major:minor weight`|`major:minor leaf_weight` pair -type WeightDevice struct { - blockIODevice - // Weight is the bandwidth rate for the device, range is from 10 to 1000 - Weight uint16 `json:"weight"` - // LeafWeight is the bandwidth rate for the device while competing with the cgroup's child cgroups, range is from 10 to 1000, cfq scheduler only - LeafWeight uint16 `json:"leafWeight"` -} - -// NewWeightDevice returns a configured WeightDevice pointer -func NewWeightDevice(major, minor int64, weight, leafWeight uint16) *WeightDevice { - wd := &WeightDevice{} - wd.Major = major - wd.Minor = minor - wd.Weight = weight - wd.LeafWeight = leafWeight - return wd -} - -// WeightString formats the struct to be writable to the cgroup specific file -func (wd *WeightDevice) WeightString() string { - return fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.Weight) -} - -// LeafWeightString formats the struct to be writable to the cgroup specific file -func (wd *WeightDevice) LeafWeightString() string { - return fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.LeafWeight) -} - -// ThrottleDevice struct holds a `major:minor rate_per_second` pair -type ThrottleDevice struct { - blockIODevice - // Rate is the IO rate limit per cgroup per device - Rate uint64 `json:"rate"` -} - -// NewThrottleDevice returns a configured ThrottleDevice pointer -func NewThrottleDevice(major, minor int64, rate uint64) *ThrottleDevice { - td := &ThrottleDevice{} - td.Major = major - td.Minor = minor - td.Rate = rate - return td -} - -// String formats the struct to be writable to the cgroup specific file -func (td *ThrottleDevice) String() string { - return fmt.Sprintf("%d:%d %d", td.Major, td.Minor, td.Rate) -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unix.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unix.go deleted file mode 100644 index 14d6289816..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unix.go +++ /dev/null @@ -1,124 +0,0 @@ -// +build linux freebsd - -package configs - -type FreezerState string - -const ( - Undefined FreezerState = "" - Frozen FreezerState = "FROZEN" - Thawed FreezerState = "THAWED" -) - -type Cgroup struct { - // Deprecated, use Path instead - Name string `json:"name,omitempty"` - - // name of parent of cgroup or slice - // Deprecated, use Path instead - Parent string `json:"parent,omitempty"` - - // Path specifies the path to cgroups that are created and/or joined by the container. - // The path is assumed to be relative to the host system cgroup mountpoint. - Path string `json:"path"` - - // ScopePrefix describes prefix for the scope name - ScopePrefix string `json:"scope_prefix"` - - // Paths represent the absolute cgroups paths to join. - // This takes precedence over Path. - Paths map[string]string - - // Resources contains various cgroups settings to apply - *Resources -} - -type Resources struct { - // If this is true allow access to any kind of device within the container. If false, allow access only to devices explicitly listed in the allowed_devices list. - // Deprecated - AllowAllDevices *bool `json:"allow_all_devices,omitempty"` - // Deprecated - AllowedDevices []*Device `json:"allowed_devices,omitempty"` - // Deprecated - DeniedDevices []*Device `json:"denied_devices,omitempty"` - - Devices []*Device `json:"devices"` - - // Memory limit (in bytes) - Memory int64 `json:"memory"` - - // Memory reservation or soft_limit (in bytes) - MemoryReservation int64 `json:"memory_reservation"` - - // Total memory usage (memory + swap); set `-1` to enable unlimited swap - MemorySwap int64 `json:"memory_swap"` - - // Kernel memory limit (in bytes) - KernelMemory int64 `json:"kernel_memory"` - - // Kernel memory limit for TCP use (in bytes) - KernelMemoryTCP int64 `json:"kernel_memory_tcp"` - - // CPU shares (relative weight vs. other containers) - CpuShares int64 `json:"cpu_shares"` - - // CPU hardcap limit (in usecs). Allowed cpu time in a given period. - CpuQuota int64 `json:"cpu_quota"` - - // CPU period to be used for hardcapping (in usecs). 0 to use system default. - CpuPeriod int64 `json:"cpu_period"` - - // How many time CPU will use in realtime scheduling (in usecs). - CpuRtRuntime int64 `json:"cpu_rt_quota"` - - // CPU period to be used for realtime scheduling (in usecs). - CpuRtPeriod int64 `json:"cpu_rt_period"` - - // CPU to use - CpusetCpus string `json:"cpuset_cpus"` - - // MEM to use - CpusetMems string `json:"cpuset_mems"` - - // Process limit; set <= `0' to disable limit. - PidsLimit int64 `json:"pids_limit"` - - // Specifies per cgroup weight, range is from 10 to 1000. - BlkioWeight uint16 `json:"blkio_weight"` - - // Specifies tasks' weight in the given cgroup while competing with the cgroup's child cgroups, range is from 10 to 1000, cfq scheduler only - BlkioLeafWeight uint16 `json:"blkio_leaf_weight"` - - // Weight per cgroup per device, can override BlkioWeight. - BlkioWeightDevice []*WeightDevice `json:"blkio_weight_device"` - - // IO read rate limit per cgroup per device, bytes per second. - BlkioThrottleReadBpsDevice []*ThrottleDevice `json:"blkio_throttle_read_bps_device"` - - // IO write rate limit per cgroup per device, bytes per second. - BlkioThrottleWriteBpsDevice []*ThrottleDevice `json:"blkio_throttle_write_bps_device"` - - // IO read rate limit per cgroup per device, IO per second. - BlkioThrottleReadIOPSDevice []*ThrottleDevice `json:"blkio_throttle_read_iops_device"` - - // IO write rate limit per cgroup per device, IO per second. - BlkioThrottleWriteIOPSDevice []*ThrottleDevice `json:"blkio_throttle_write_iops_device"` - - // set the freeze value for the process - Freezer FreezerState `json:"freezer"` - - // Hugetlb limit (in bytes) - HugetlbLimit []*HugepageLimit `json:"hugetlb_limit"` - - // Whether to disable OOM Killer - OomKillDisable bool `json:"oom_kill_disable"` - - // Tuning swappiness behaviour per cgroup - MemorySwappiness *int64 `json:"memory_swappiness"` - - // Set priority of network traffic for container - NetPrioIfpriomap []*IfPrioMap `json:"net_prio_ifpriomap"` - - // Set class identifier for container's network packets - NetClsClassid uint32 `json:"net_cls_classid_u"` -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unsupported.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unsupported.go deleted file mode 100644 index 95e2830a43..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_unsupported.go +++ /dev/null @@ -1,6 +0,0 @@ -// +build !windows,!linux,!freebsd - -package configs - -type Cgroup struct { -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_windows.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_windows.go deleted file mode 100644 index d74847b0db..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/cgroup_windows.go +++ /dev/null @@ -1,6 +0,0 @@ -package configs - -// TODO Windows: This can ultimately be entirely factored out on Windows as -// cgroups are a Unix-specific construct. -type Cgroup struct { -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go deleted file mode 100644 index fdfe248c73..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/config.go +++ /dev/null @@ -1,332 +0,0 @@ -package configs - -import ( - "bytes" - "encoding/json" - "fmt" - "os/exec" - "time" - - "github.com/Sirupsen/logrus" -) - -type Rlimit struct { - Type int `json:"type"` - Hard uint64 `json:"hard"` - Soft uint64 `json:"soft"` -} - -// IDMap represents UID/GID Mappings for User Namespaces. -type IDMap struct { - ContainerID int `json:"container_id"` - HostID int `json:"host_id"` - Size int `json:"size"` -} - -// Seccomp represents syscall restrictions -// By default, only the native architecture of the kernel is allowed to be used -// for syscalls. Additional architectures can be added by specifying them in -// Architectures. -type Seccomp struct { - DefaultAction Action `json:"default_action"` - Architectures []string `json:"architectures"` - Syscalls []*Syscall `json:"syscalls"` -} - -// Action is taken upon rule match in Seccomp -type Action int - -const ( - Kill Action = iota + 1 - Errno - Trap - Allow - Trace -) - -// Operator is a comparison operator to be used when matching syscall arguments in Seccomp -type Operator int - -const ( - EqualTo Operator = iota + 1 - NotEqualTo - GreaterThan - GreaterThanOrEqualTo - LessThan - LessThanOrEqualTo - MaskEqualTo -) - -// Arg is a rule to match a specific syscall argument in Seccomp -type Arg struct { - Index uint `json:"index"` - Value uint64 `json:"value"` - ValueTwo uint64 `json:"value_two"` - Op Operator `json:"op"` -} - -// Syscall is a rule to match a syscall in Seccomp -type Syscall struct { - Name string `json:"name"` - Action Action `json:"action"` - Args []*Arg `json:"args"` -} - -// TODO Windows. Many of these fields should be factored out into those parts -// which are common across platforms, and those which are platform specific. - -// Config defines configuration options for executing a process inside a contained environment. -type Config struct { - // NoPivotRoot will use MS_MOVE and a chroot to jail the process into the container's rootfs - // This is a common option when the container is running in ramdisk - NoPivotRoot bool `json:"no_pivot_root"` - - // ParentDeathSignal specifies the signal that is sent to the container's process in the case - // that the parent process dies. - ParentDeathSignal int `json:"parent_death_signal"` - - // Path to a directory containing the container's root filesystem. - Rootfs string `json:"rootfs"` - - // Readonlyfs will remount the container's rootfs as readonly where only externally mounted - // bind mounts are writtable. - Readonlyfs bool `json:"readonlyfs"` - - // Specifies the mount propagation flags to be applied to /. - RootPropagation int `json:"rootPropagation"` - - // Mounts specify additional source and destination paths that will be mounted inside the container's - // rootfs and mount namespace if specified - Mounts []*Mount `json:"mounts"` - - // The device nodes that should be automatically created within the container upon container start. Note, make sure that the node is marked as allowed in the cgroup as well! - Devices []*Device `json:"devices"` - - MountLabel string `json:"mount_label"` - - // Hostname optionally sets the container's hostname if provided - Hostname string `json:"hostname"` - - // Namespaces specifies the container's namespaces that it should setup when cloning the init process - // If a namespace is not provided that namespace is shared from the container's parent process - Namespaces Namespaces `json:"namespaces"` - - // Capabilities specify the capabilities to keep when executing the process inside the container - // All capbilities not specified will be dropped from the processes capability mask - Capabilities []string `json:"capabilities"` - - // Networks specifies the container's network setup to be created - Networks []*Network `json:"networks"` - - // Routes can be specified to create entries in the route table as the container is started - Routes []*Route `json:"routes"` - - // Cgroups specifies specific cgroup settings for the various subsystems that the container is - // placed into to limit the resources the container has available - Cgroups *Cgroup `json:"cgroups"` - - // AppArmorProfile specifies the profile to apply to the process running in the container and is - // change at the time the process is execed - AppArmorProfile string `json:"apparmor_profile,omitempty"` - - // ProcessLabel specifies the label to apply to the process running in the container. It is - // commonly used by selinux - ProcessLabel string `json:"process_label,omitempty"` - - // Rlimits specifies the resource limits, such as max open files, to set in the container - // If Rlimits are not set, the container will inherit rlimits from the parent process - Rlimits []Rlimit `json:"rlimits,omitempty"` - - // OomScoreAdj specifies the adjustment to be made by the kernel when calculating oom scores - // for a process. Valid values are between the range [-1000, '1000'], where processes with - // higher scores are preferred for being killed. - // More information about kernel oom score calculation here: https://lwn.net/Articles/317814/ - OomScoreAdj int `json:"oom_score_adj"` - - // UidMappings is an array of User ID mappings for User Namespaces - UidMappings []IDMap `json:"uid_mappings"` - - // GidMappings is an array of Group ID mappings for User Namespaces - GidMappings []IDMap `json:"gid_mappings"` - - // MaskPaths specifies paths within the container's rootfs to mask over with a bind - // mount pointing to /dev/null as to prevent reads of the file. - MaskPaths []string `json:"mask_paths"` - - // ReadonlyPaths specifies paths within the container's rootfs to remount as read-only - // so that these files prevent any writes. - ReadonlyPaths []string `json:"readonly_paths"` - - // Sysctl is a map of properties and their values. It is the equivalent of using - // sysctl -w my.property.name value in Linux. - Sysctl map[string]string `json:"sysctl"` - - // Seccomp allows actions to be taken whenever a syscall is made within the container. - // A number of rules are given, each having an action to be taken if a syscall matches it. - // A default action to be taken if no rules match is also given. - Seccomp *Seccomp `json:"seccomp"` - - // NoNewPrivileges controls whether processes in the container can gain additional privileges. - NoNewPrivileges bool `json:"no_new_privileges,omitempty"` - - // Hooks are a collection of actions to perform at various container lifecycle events. - // CommandHooks are serialized to JSON, but other hooks are not. - Hooks *Hooks - - // Version is the version of opencontainer specification that is supported. - Version string `json:"version"` - - // Labels are user defined metadata that is stored in the config and populated on the state - Labels []string `json:"labels"` - - // NoNewKeyring will not allocated a new session keyring for the container. It will use the - // callers keyring in this case. - NoNewKeyring bool `json:"no_new_keyring"` -} - -type Hooks struct { - // Prestart commands are executed after the container namespaces are created, - // but before the user supplied command is executed from init. - Prestart []Hook - - // Poststart commands are executed after the container init process starts. - Poststart []Hook - - // Poststop commands are executed after the container init process exits. - Poststop []Hook -} - -func (hooks *Hooks) UnmarshalJSON(b []byte) error { - var state struct { - Prestart []CommandHook - Poststart []CommandHook - Poststop []CommandHook - } - - if err := json.Unmarshal(b, &state); err != nil { - return err - } - - deserialize := func(shooks []CommandHook) (hooks []Hook) { - for _, shook := range shooks { - hooks = append(hooks, shook) - } - - return hooks - } - - hooks.Prestart = deserialize(state.Prestart) - hooks.Poststart = deserialize(state.Poststart) - hooks.Poststop = deserialize(state.Poststop) - return nil -} - -func (hooks Hooks) MarshalJSON() ([]byte, error) { - serialize := func(hooks []Hook) (serializableHooks []CommandHook) { - for _, hook := range hooks { - switch chook := hook.(type) { - case CommandHook: - serializableHooks = append(serializableHooks, chook) - default: - logrus.Warnf("cannot serialize hook of type %T, skipping", hook) - } - } - - return serializableHooks - } - - return json.Marshal(map[string]interface{}{ - "prestart": serialize(hooks.Prestart), - "poststart": serialize(hooks.Poststart), - "poststop": serialize(hooks.Poststop), - }) -} - -// HookState is the payload provided to a hook on execution. -type HookState struct { - Version string `json:"ociVersion"` - ID string `json:"id"` - Pid int `json:"pid"` - Root string `json:"root"` - BundlePath string `json:"bundlePath"` -} - -type Hook interface { - // Run executes the hook with the provided state. - Run(HookState) error -} - -// NewFunctionHook will call the provided function when the hook is run. -func NewFunctionHook(f func(HookState) error) FuncHook { - return FuncHook{ - run: f, - } -} - -type FuncHook struct { - run func(HookState) error -} - -func (f FuncHook) Run(s HookState) error { - return f.run(s) -} - -type Command struct { - Path string `json:"path"` - Args []string `json:"args"` - Env []string `json:"env"` - Dir string `json:"dir"` - Timeout *time.Duration `json:"timeout"` -} - -// NewCommandHook will execute the provided command when the hook is run. -func NewCommandHook(cmd Command) CommandHook { - return CommandHook{ - Command: cmd, - } -} - -type CommandHook struct { - Command -} - -func (c Command) Run(s HookState) error { - b, err := json.Marshal(s) - if err != nil { - return err - } - var stdout, stderr bytes.Buffer - cmd := exec.Cmd{ - Path: c.Path, - Args: c.Args, - Env: c.Env, - Stdin: bytes.NewReader(b), - Stdout: &stdout, - Stderr: &stderr, - } - if err := cmd.Start(); err != nil { - return err - } - errC := make(chan error, 1) - go func() { - err := cmd.Wait() - if err != nil { - err = fmt.Errorf("error running hook: %v, stdout: %s, stderr: %s", err, stdout.String(), stderr.String()) - } - errC <- err - }() - var timerCh <-chan time.Time - if c.Timeout != nil { - timer := time.NewTimer(*c.Timeout) - defer timer.Stop() - timerCh = timer.C - } - select { - case err := <-errC: - return err - case <-timerCh: - cmd.Process.Kill() - cmd.Wait() - return fmt.Errorf("hook ran past specified timeout of %.1fs", c.Timeout.Seconds()) - } -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/config_unix.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/config_unix.go deleted file mode 100644 index a60554a7b9..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/config_unix.go +++ /dev/null @@ -1,51 +0,0 @@ -// +build freebsd linux - -package configs - -import "fmt" - -// HostUID gets the root uid for the process on host which could be non-zero -// when user namespaces are enabled. -func (c Config) HostUID() (int, error) { - if c.Namespaces.Contains(NEWUSER) { - if c.UidMappings == nil { - return -1, fmt.Errorf("User namespaces enabled, but no user mappings found.") - } - id, found := c.hostIDFromMapping(0, c.UidMappings) - if !found { - return -1, fmt.Errorf("User namespaces enabled, but no root user mapping found.") - } - return id, nil - } - // Return default root uid 0 - return 0, nil -} - -// HostGID gets the root gid for the process on host which could be non-zero -// when user namespaces are enabled. -func (c Config) HostGID() (int, error) { - if c.Namespaces.Contains(NEWUSER) { - if c.GidMappings == nil { - return -1, fmt.Errorf("User namespaces enabled, but no gid mappings found.") - } - id, found := c.hostIDFromMapping(0, c.GidMappings) - if !found { - return -1, fmt.Errorf("User namespaces enabled, but no root group mapping found.") - } - return id, nil - } - // Return default root gid 0 - return 0, nil -} - -// Utility function that gets a host ID for a container ID from user namespace map -// if that ID is present in the map. -func (c Config) hostIDFromMapping(containerID int, uMap []IDMap) (int, bool) { - for _, m := range uMap { - if (containerID >= m.ContainerID) && (containerID <= (m.ContainerID + m.Size - 1)) { - hostID := m.HostID + (containerID - m.ContainerID) - return hostID, true - } - } - return -1, false -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/device.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/device.go deleted file mode 100644 index 8701bb212d..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/device.go +++ /dev/null @@ -1,57 +0,0 @@ -package configs - -import ( - "fmt" - "os" -) - -const ( - Wildcard = -1 -) - -// TODO Windows: This can be factored out in the future - -type Device struct { - // Device type, block, char, etc. - Type rune `json:"type"` - - // Path to the device. - Path string `json:"path"` - - // Major is the device's major number. - Major int64 `json:"major"` - - // Minor is the device's minor number. - Minor int64 `json:"minor"` - - // Cgroup permissions format, rwm. - Permissions string `json:"permissions"` - - // FileMode permission bits for the device. - FileMode os.FileMode `json:"file_mode"` - - // Uid of the device. - Uid uint32 `json:"uid"` - - // Gid of the device. - Gid uint32 `json:"gid"` - - // Write the file to the allowed list - Allow bool `json:"allow"` -} - -func (d *Device) CgroupString() string { - return fmt.Sprintf("%c %s:%s %s", d.Type, deviceNumberString(d.Major), deviceNumberString(d.Minor), d.Permissions) -} - -func (d *Device) Mkdev() int { - return int((d.Major << 8) | (d.Minor & 0xff) | ((d.Minor & 0xfff00) << 12)) -} - -// deviceNumberString converts the device number to a string return result. -func deviceNumberString(number int64) string { - if number == Wildcard { - return "*" - } - return fmt.Sprint(number) -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/device_defaults.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/device_defaults.go deleted file mode 100644 index 4d348d217e..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/device_defaults.go +++ /dev/null @@ -1,111 +0,0 @@ -// +build linux freebsd - -package configs - -var ( - // DefaultSimpleDevices are devices that are to be both allowed and created. - DefaultSimpleDevices = []*Device{ - // /dev/null and zero - { - Path: "/dev/null", - Type: 'c', - Major: 1, - Minor: 3, - Permissions: "rwm", - FileMode: 0666, - }, - { - Path: "/dev/zero", - Type: 'c', - Major: 1, - Minor: 5, - Permissions: "rwm", - FileMode: 0666, - }, - - { - Path: "/dev/full", - Type: 'c', - Major: 1, - Minor: 7, - Permissions: "rwm", - FileMode: 0666, - }, - - // consoles and ttys - { - Path: "/dev/tty", - Type: 'c', - Major: 5, - Minor: 0, - Permissions: "rwm", - FileMode: 0666, - }, - - // /dev/urandom,/dev/random - { - Path: "/dev/urandom", - Type: 'c', - Major: 1, - Minor: 9, - Permissions: "rwm", - FileMode: 0666, - }, - { - Path: "/dev/random", - Type: 'c', - Major: 1, - Minor: 8, - Permissions: "rwm", - FileMode: 0666, - }, - } - DefaultAllowedDevices = append([]*Device{ - // allow mknod for any device - { - Type: 'c', - Major: Wildcard, - Minor: Wildcard, - Permissions: "m", - }, - { - Type: 'b', - Major: Wildcard, - Minor: Wildcard, - Permissions: "m", - }, - - { - Path: "/dev/console", - Type: 'c', - Major: 5, - Minor: 1, - Permissions: "rwm", - }, - // /dev/pts/ - pts namespaces are "coming soon" - { - Path: "", - Type: 'c', - Major: 136, - Minor: Wildcard, - Permissions: "rwm", - }, - { - Path: "", - Type: 'c', - Major: 5, - Minor: 2, - Permissions: "rwm", - }, - - // tuntap - { - Path: "", - Type: 'c', - Major: 10, - Minor: 200, - Permissions: "rwm", - }, - }, DefaultSimpleDevices...) - DefaultAutoCreatedDevices = append([]*Device{}, DefaultSimpleDevices...) -) diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/hugepage_limit.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/hugepage_limit.go deleted file mode 100644 index d30216380b..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/hugepage_limit.go +++ /dev/null @@ -1,9 +0,0 @@ -package configs - -type HugepageLimit struct { - // which type of hugepage to limit. - Pagesize string `json:"page_size"` - - // usage limit for hugepage. - Limit uint64 `json:"limit"` -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/interface_priority_map.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/interface_priority_map.go deleted file mode 100644 index 9a0395eaf5..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/interface_priority_map.go +++ /dev/null @@ -1,14 +0,0 @@ -package configs - -import ( - "fmt" -) - -type IfPrioMap struct { - Interface string `json:"interface"` - Priority int64 `json:"priority"` -} - -func (i *IfPrioMap) CgroupString() string { - return fmt.Sprintf("%s %d", i.Interface, i.Priority) -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go deleted file mode 100644 index 670757ddb5..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/mount.go +++ /dev/null @@ -1,39 +0,0 @@ -package configs - -const ( - // EXT_COPYUP is a directive to copy up the contents of a directory when - // a tmpfs is mounted over it. - EXT_COPYUP = 1 << iota -) - -type Mount struct { - // Source path for the mount. - Source string `json:"source"` - - // Destination path for the mount inside the container. - Destination string `json:"destination"` - - // Device the mount is for. - Device string `json:"device"` - - // Mount flags. - Flags int `json:"flags"` - - // Propagation Flags - PropagationFlags []int `json:"propagation_flags"` - - // Mount data applied to the mount. - Data string `json:"data"` - - // Relabel source if set, "z" indicates shared, "Z" indicates unshared. - Relabel string `json:"relabel"` - - // Extensions are additional flags that are specific to runc. - Extensions int `json:"extensions"` - - // Optional Command to be run before Source is mounted. - PremountCmds []Command `json:"premount_cmds"` - - // Optional Command to be run after Source is mounted. - PostmountCmds []Command `json:"postmount_cmds"` -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces.go deleted file mode 100644 index a3329a31a9..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces.go +++ /dev/null @@ -1,5 +0,0 @@ -package configs - -type NamespaceType string - -type Namespaces []Namespace diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall.go deleted file mode 100644 index fb4b852222..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall.go +++ /dev/null @@ -1,31 +0,0 @@ -// +build linux - -package configs - -import "syscall" - -func (n *Namespace) Syscall() int { - return namespaceInfo[n.Type] -} - -var namespaceInfo = map[NamespaceType]int{ - NEWNET: syscall.CLONE_NEWNET, - NEWNS: syscall.CLONE_NEWNS, - NEWUSER: syscall.CLONE_NEWUSER, - NEWIPC: syscall.CLONE_NEWIPC, - NEWUTS: syscall.CLONE_NEWUTS, - NEWPID: syscall.CLONE_NEWPID, -} - -// CloneFlags parses the container's Namespaces options to set the correct -// flags on clone, unshare. This function returns flags only for new namespaces. -func (n *Namespaces) CloneFlags() uintptr { - var flag int - for _, v := range *n { - if v.Path != "" { - continue - } - flag |= namespaceInfo[v.Type] - } - return uintptr(flag) -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall_unsupported.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall_unsupported.go deleted file mode 100644 index 0547223a9d..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_syscall_unsupported.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build !linux,!windows - -package configs - -func (n *Namespace) Syscall() int { - panic("No namespace syscall support") - return 0 -} - -// CloneFlags parses the container's Namespaces options to set the correct -// flags on clone, unshare. This function returns flags only for new namespaces. -func (n *Namespaces) CloneFlags() uintptr { - panic("No namespace syscall support") - return uintptr(0) -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go deleted file mode 100644 index 8beba9d300..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unix.go +++ /dev/null @@ -1,127 +0,0 @@ -// +build linux freebsd - -package configs - -import ( - "fmt" - "os" - "sync" -) - -const ( - NEWNET NamespaceType = "NEWNET" - NEWPID NamespaceType = "NEWPID" - NEWNS NamespaceType = "NEWNS" - NEWUTS NamespaceType = "NEWUTS" - NEWIPC NamespaceType = "NEWIPC" - NEWUSER NamespaceType = "NEWUSER" -) - -var ( - nsLock sync.Mutex - supportedNamespaces = make(map[NamespaceType]bool) -) - -// NsName converts the namespace type to its filename -func NsName(ns NamespaceType) string { - switch ns { - case NEWNET: - return "net" - case NEWNS: - return "mnt" - case NEWPID: - return "pid" - case NEWIPC: - return "ipc" - case NEWUSER: - return "user" - case NEWUTS: - return "uts" - } - return "" -} - -// IsNamespaceSupported returns whether a namespace is available or -// not -func IsNamespaceSupported(ns NamespaceType) bool { - nsLock.Lock() - defer nsLock.Unlock() - supported, ok := supportedNamespaces[ns] - if ok { - return supported - } - nsFile := NsName(ns) - // if the namespace type is unknown, just return false - if nsFile == "" { - return false - } - _, err := os.Stat(fmt.Sprintf("/proc/self/ns/%s", nsFile)) - // a namespace is supported if it exists and we have permissions to read it - supported = err == nil - supportedNamespaces[ns] = supported - return supported -} - -func NamespaceTypes() []NamespaceType { - return []NamespaceType{ - NEWNET, - NEWPID, - NEWNS, - NEWUTS, - NEWIPC, - NEWUSER, - } -} - -// Namespace defines configuration for each namespace. It specifies an -// alternate path that is able to be joined via setns. -type Namespace struct { - Type NamespaceType `json:"type"` - Path string `json:"path"` -} - -func (n *Namespace) GetPath(pid int) string { - if n.Path != "" { - return n.Path - } - return fmt.Sprintf("/proc/%d/ns/%s", pid, NsName(n.Type)) -} - -func (n *Namespaces) Remove(t NamespaceType) bool { - i := n.index(t) - if i == -1 { - return false - } - *n = append((*n)[:i], (*n)[i+1:]...) - return true -} - -func (n *Namespaces) Add(t NamespaceType, path string) { - i := n.index(t) - if i == -1 { - *n = append(*n, Namespace{Type: t, Path: path}) - return - } - (*n)[i].Path = path -} - -func (n *Namespaces) index(t NamespaceType) int { - for i, ns := range *n { - if ns.Type == t { - return i - } - } - return -1 -} - -func (n *Namespaces) Contains(t NamespaceType) bool { - return n.index(t) != -1 -} - -func (n *Namespaces) PathOf(t NamespaceType) string { - i := n.index(t) - if i == -1 { - return "" - } - return (*n)[i].Path -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unsupported.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unsupported.go deleted file mode 100644 index 9a74033cea..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/namespaces_unsupported.go +++ /dev/null @@ -1,8 +0,0 @@ -// +build !linux,!freebsd - -package configs - -// Namespace defines configuration for each namespace. It specifies an -// alternate path that is able to be joined via setns. -type Namespace struct { -} diff --git a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/network.go b/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/network.go deleted file mode 100644 index ccdb228e14..0000000000 --- a/components/cli/vendor/github.com/opencontainers/runc/libcontainer/configs/network.go +++ /dev/null @@ -1,72 +0,0 @@ -package configs - -// Network defines configuration for a container's networking stack -// -// The network configuration can be omitted from a container causing the -// container to be setup with the host's networking stack -type Network struct { - // Type sets the networks type, commonly veth and loopback - Type string `json:"type"` - - // Name of the network interface - Name string `json:"name"` - - // The bridge to use. - Bridge string `json:"bridge"` - - // MacAddress contains the MAC address to set on the network interface - MacAddress string `json:"mac_address"` - - // Address contains the IPv4 and mask to set on the network interface - Address string `json:"address"` - - // Gateway sets the gateway address that is used as the default for the interface - Gateway string `json:"gateway"` - - // IPv6Address contains the IPv6 and mask to set on the network interface - IPv6Address string `json:"ipv6_address"` - - // IPv6Gateway sets the ipv6 gateway address that is used as the default for the interface - IPv6Gateway string `json:"ipv6_gateway"` - - // Mtu sets the mtu value for the interface and will be mirrored on both the host and - // container's interfaces if a pair is created, specifically in the case of type veth - // Note: This does not apply to loopback interfaces. - Mtu int `json:"mtu"` - - // TxQueueLen sets the tx_queuelen value for the interface and will be mirrored on both the host and - // container's interfaces if a pair is created, specifically in the case of type veth - // Note: This does not apply to loopback interfaces. - TxQueueLen int `json:"txqueuelen"` - - // HostInterfaceName is a unique name of a veth pair that resides on in the host interface of the - // container. - HostInterfaceName string `json:"host_interface_name"` - - // HairpinMode specifies if hairpin NAT should be enabled on the virtual interface - // bridge port in the case of type veth - // Note: This is unsupported on some systems. - // Note: This does not apply to loopback interfaces. - HairpinMode bool `json:"hairpin_mode"` -} - -// Routes can be specified to create entries in the route table as the container is started -// -// All of destination, source, and gateway should be either IPv4 or IPv6. -// One of the three options must be present, and omitted entries will use their -// IP family default for the route table. For IPv4 for example, setting the -// gateway to 1.2.3.4 and the interface to eth0 will set up a standard -// destination of 0.0.0.0(or *) when viewed in the route table. -type Route struct { - // Sets the destination and mask, should be a CIDR. Accepts IPv4 and IPv6 - Destination string `json:"destination"` - - // Sets the source and mask, should be a CIDR. Accepts IPv4 and IPv6 - Source string `json:"source"` - - // Sets the gateway. Accepts IPv4 and IPv6 - Gateway string `json:"gateway"` - - // The device to set this route up for, for example: eth0 - InterfaceName string `json:"interface_name"` -} diff --git a/components/cli/vendor/github.com/opencontainers/selinux/README.md b/components/cli/vendor/github.com/opencontainers/selinux/README.md deleted file mode 100644 index 043a929371..0000000000 --- a/components/cli/vendor/github.com/opencontainers/selinux/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# selinux - -[![GoDoc](https://godoc.org/github.com/opencontainers/selinux?status.svg)](https://godoc.org/github.com/opencontainers/selinux) [![Go Report Card](https://goreportcard.com/badge/github.com/opencontainers/selinux)](https://goreportcard.com/report/github.com/opencontainers/selinux) [![Build Status](https://travis-ci.org/opencontainers/selinux.svg?branch=master)](https://travis-ci.org/opencontainers/selinux) - -Common SELinux package used across the container ecosystem. - -Please see the [godoc](https://godoc.org/github.com/opencontainers/selinux) for more information. diff --git a/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/label/label.go b/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/label/label.go deleted file mode 100644 index 6cfc5fded8..0000000000 --- a/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/label/label.go +++ /dev/null @@ -1,84 +0,0 @@ -// +build !selinux !linux - -package label - -// InitLabels returns the process label and file labels to be used within -// the container. A list of options can be passed into this function to alter -// the labels. -func InitLabels(options []string) (string, string, error) { - return "", "", nil -} - -func GetROMountLabel() string { - return "" -} - -func GenLabels(options string) (string, string, error) { - return "", "", nil -} - -func FormatMountLabel(src string, mountLabel string) string { - return src -} - -func SetProcessLabel(processLabel string) error { - return nil -} - -func GetFileLabel(path string) (string, error) { - return "", nil -} - -func SetFileLabel(path string, fileLabel string) error { - return nil -} - -func SetFileCreateLabel(fileLabel string) error { - return nil -} - -func Relabel(path string, fileLabel string, shared bool) error { - return nil -} - -func GetPidLabel(pid int) (string, error) { - return "", nil -} - -func Init() { -} - -func ReserveLabel(label string) error { - return nil -} - -func ReleaseLabel(label string) error { - return nil -} - -// DupSecOpt takes a process label and returns security options that -// can be used to set duplicate labels on future container processes -func DupSecOpt(src string) []string { - return nil -} - -// DisableSecOpt returns a security opt that can disable labeling -// support for future container processes -func DisableSecOpt() []string { - return nil -} - -// Validate checks that the label does not include unexpected options -func Validate(label string) error { - return nil -} - -// RelabelNeeded checks whether the user requested a relabel -func RelabelNeeded(label string) bool { - return false -} - -// IsShared checks that the label includes a "shared" mark -func IsShared(label string) bool { - return false -} diff --git a/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go b/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go deleted file mode 100644 index 569dcf0841..0000000000 --- a/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/label/label_selinux.go +++ /dev/null @@ -1,204 +0,0 @@ -// +build selinux,linux - -package label - -import ( - "fmt" - "strings" - - "github.com/opencontainers/selinux/go-selinux" -) - -// Valid Label Options -var validOptions = map[string]bool{ - "disable": true, - "type": true, - "user": true, - "role": true, - "level": true, -} - -var ErrIncompatibleLabel = fmt.Errorf("Bad SELinux option z and Z can not be used together") - -// InitLabels returns the process label and file labels to be used within -// the container. A list of options can be passed into this function to alter -// the labels. The labels returned will include a random MCS String, that is -// guaranteed to be unique. -func InitLabels(options []string) (string, string, error) { - if !selinux.GetEnabled() { - return "", "", nil - } - processLabel, mountLabel := selinux.ContainerLabels() - if processLabel != "" { - pcon := selinux.NewContext(processLabel) - mcon := selinux.NewContext(mountLabel) - for _, opt := range options { - if opt == "disable" { - return "", "", nil - } - if i := strings.Index(opt, ":"); i == -1 { - return "", "", fmt.Errorf("Bad label option %q, valid options 'disable' or \n'user, role, level, type' followed by ':' and a value", opt) - } - con := strings.SplitN(opt, ":", 2) - if !validOptions[con[0]] { - return "", "", fmt.Errorf("Bad label option %q, valid options 'disable, user, role, level, type'", con[0]) - - } - pcon[con[0]] = con[1] - if con[0] == "level" || con[0] == "user" { - mcon[con[0]] = con[1] - } - } - processLabel = pcon.Get() - mountLabel = mcon.Get() - } - return processLabel, mountLabel, nil -} - -func ROMountLabel() string { - return selinux.ROFileLabel() -} - -// DEPRECATED: The GenLabels function is only to be used during the transition to the official API. -func GenLabels(options string) (string, string, error) { - return InitLabels(strings.Fields(options)) -} - -// FormatMountLabel returns a string to be used by the mount command. -// The format of this string will be used to alter the labeling of the mountpoint. -// The string returned is suitable to be used as the options field of the mount command. -// If you need to have additional mount point options, you can pass them in as -// the first parameter. Second parameter is the label that you wish to apply -// to all content in the mount point. -func FormatMountLabel(src, mountLabel string) string { - if mountLabel != "" { - switch src { - case "": - src = fmt.Sprintf("context=%q", mountLabel) - default: - src = fmt.Sprintf("%s,context=%q", src, mountLabel) - } - } - return src -} - -// SetProcessLabel takes a process label and tells the kernel to assign the -// label to the next program executed by the current process. -func SetProcessLabel(processLabel string) error { - if processLabel == "" { - return nil - } - return selinux.SetExecLabel(processLabel) -} - -// ProcessLabel returns the process label that the kernel will assign -// to the next program executed by the current process. If "" is returned -// this indicates that the default labeling will happen for the process. -func ProcessLabel() (string, error) { - return selinux.ExecLabel() -} - -// GetFileLabel returns the label for specified path -func FileLabel(path string) (string, error) { - return selinux.FileLabel(path) -} - -// SetFileLabel modifies the "path" label to the specified file label -func SetFileLabel(path string, fileLabel string) error { - if selinux.GetEnabled() && fileLabel != "" { - return selinux.SetFileLabel(path, fileLabel) - } - return nil -} - -// SetFileCreateLabel tells the kernel the label for all files to be created -func SetFileCreateLabel(fileLabel string) error { - if selinux.GetEnabled() { - return selinux.SetFSCreateLabel(fileLabel) - } - return nil -} - -// Relabel changes the label of path to the filelabel string. -// It changes the MCS label to s0 if shared is true. -// This will allow all containers to share the content. -func Relabel(path string, fileLabel string, shared bool) error { - if !selinux.GetEnabled() { - return nil - } - - if fileLabel == "" { - return nil - } - - exclude_paths := map[string]bool{"/": true, "/usr": true, "/etc": true} - if exclude_paths[path] { - return fmt.Errorf("SELinux relabeling of %s is not allowed", path) - } - - if shared { - c := selinux.NewContext(fileLabel) - c["level"] = "s0" - fileLabel = c.Get() - } - if err := selinux.Chcon(path, fileLabel, true); err != nil { - return err - } - return nil -} - -// PidLabel will return the label of the process running with the specified pid -func PidLabel(pid int) (string, error) { - return selinux.PidLabel(pid) -} - -// Init initialises the labeling system -func Init() { - selinux.GetEnabled() -} - -// ReserveLabel will record the fact that the MCS label has already been used. -// This will prevent InitLabels from using the MCS label in a newly created -// container -func ReserveLabel(label string) error { - selinux.ReserveLabel(label) - return nil -} - -// ReleaseLabel will remove the reservation of the MCS label. -// This will allow InitLabels to use the MCS label in a newly created -// containers -func ReleaseLabel(label string) error { - selinux.ReleaseLabel(label) - return nil -} - -// DupSecOpt takes a process label and returns security options that -// can be used to set duplicate labels on future container processes -func DupSecOpt(src string) []string { - return selinux.DupSecOpt(src) -} - -// DisableSecOpt returns a security opt that can disable labeling -// support for future container processes -func DisableSecOpt() []string { - return selinux.DisableSecOpt() -} - -// Validate checks that the label does not include unexpected options -func Validate(label string) error { - if strings.Contains(label, "z") && strings.Contains(label, "Z") { - return ErrIncompatibleLabel - } - return nil -} - -// RelabelNeeded checks whether the user requested a relabel -func RelabelNeeded(label string) bool { - return strings.Contains(label, "z") || strings.Contains(label, "Z") -} - -// IsShared checks that the label includes a "shared" mark -func IsShared(label string) bool { - return strings.Contains(label, "z") -} diff --git a/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go b/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go deleted file mode 100644 index 4cf2c45de7..0000000000 --- a/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/selinux.go +++ /dev/null @@ -1,593 +0,0 @@ -// +build linux - -package selinux - -import ( - "bufio" - "crypto/rand" - "encoding/binary" - "fmt" - "io" - "os" - "path/filepath" - "regexp" - "strconv" - "strings" - "sync" - "syscall" -) - -const ( - // Enforcing constant indicate SELinux is in enforcing mode - Enforcing = 1 - // Permissive constant to indicate SELinux is in permissive mode - Permissive = 0 - // Disabled constant to indicate SELinux is disabled - Disabled = -1 - selinuxDir = "/etc/selinux/" - selinuxConfig = selinuxDir + "config" - selinuxTypeTag = "SELINUXTYPE" - selinuxTag = "SELINUX" - selinuxPath = "/sys/fs/selinux" - xattrNameSelinux = "security.selinux" - stRdOnly = 0x01 -) - -type selinuxState struct { - enabledSet bool - enabled bool - selinuxfsSet bool - selinuxfs string - mcsList map[string]bool - sync.Mutex -} - -var ( - assignRegex = regexp.MustCompile(`^([^=]+)=(.*)$`) - state = selinuxState{ - mcsList: make(map[string]bool), - } -) - -// Context is a representation of the SELinux label broken into 4 parts -type Context map[string]string - -func (s *selinuxState) setEnable(enabled bool) bool { - s.Lock() - defer s.Unlock() - s.enabledSet = true - s.enabled = enabled - return s.enabled -} - -func (s *selinuxState) getEnabled() bool { - s.Lock() - enabled := s.enabled - enabledSet := s.enabledSet - s.Unlock() - if enabledSet { - return enabled - } - - enabled = false - if fs := getSelinuxMountPoint(); fs != "" { - if con, _ := CurrentLabel(); con != "kernel" { - enabled = true - } - } - return s.setEnable(enabled) -} - -// SetDisabled disables selinux support for the package -func SetDisabled() { - state.setEnable(false) -} - -func (s *selinuxState) setSELinuxfs(selinuxfs string) string { - s.Lock() - defer s.Unlock() - s.selinuxfsSet = true - s.selinuxfs = selinuxfs - return s.selinuxfs -} - -func (s *selinuxState) getSELinuxfs() string { - s.Lock() - selinuxfs := s.selinuxfs - selinuxfsSet := s.selinuxfsSet - s.Unlock() - if selinuxfsSet { - return selinuxfs - } - - selinuxfs = "" - f, err := os.Open("/proc/self/mountinfo") - if err != nil { - return selinuxfs - } - defer f.Close() - - scanner := bufio.NewScanner(f) - for scanner.Scan() { - txt := scanner.Text() - // Safe as mountinfo encodes mountpoints with spaces as \040. - sepIdx := strings.Index(txt, " - ") - if sepIdx == -1 { - continue - } - if !strings.Contains(txt[sepIdx:], "selinuxfs") { - continue - } - fields := strings.Split(txt, " ") - if len(fields) < 5 { - continue - } - selinuxfs = fields[4] - break - } - - if selinuxfs != "" { - var buf syscall.Statfs_t - syscall.Statfs(selinuxfs, &buf) - if (buf.Flags & stRdOnly) == 1 { - selinuxfs = "" - } - } - return s.setSELinuxfs(selinuxfs) -} - -// getSelinuxMountPoint returns the path to the mountpoint of an selinuxfs -// filesystem or an empty string if no mountpoint is found. Selinuxfs is -// a proc-like pseudo-filesystem that exposes the selinux policy API to -// processes. The existence of an selinuxfs mount is used to determine -// whether selinux is currently enabled or not. -func getSelinuxMountPoint() string { - return state.getSELinuxfs() -} - -// GetEnabled returns whether selinux is currently enabled. -func GetEnabled() bool { - return state.getEnabled() -} - -func readConfig(target string) (value string) { - var ( - val, key string - bufin *bufio.Reader - ) - - in, err := os.Open(selinuxConfig) - if err != nil { - return "" - } - defer in.Close() - - bufin = bufio.NewReader(in) - - for done := false; !done; { - var line string - if line, err = bufin.ReadString('\n'); err != nil { - if err != io.EOF { - return "" - } - done = true - } - line = strings.TrimSpace(line) - if len(line) == 0 { - // Skip blank lines - continue - } - if line[0] == ';' || line[0] == '#' { - // Skip comments - continue - } - if groups := assignRegex.FindStringSubmatch(line); groups != nil { - key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2]) - if key == target { - return strings.Trim(val, "\"") - } - } - } - return "" -} - -func getSELinuxPolicyRoot() string { - return selinuxDir + readConfig(selinuxTypeTag) -} - -func readCon(name string) (string, error) { - var val string - - in, err := os.Open(name) - if err != nil { - return "", err - } - defer in.Close() - - _, err = fmt.Fscanf(in, "%s", &val) - return val, err -} - -// SetFileLabel sets the SELinux label for this path or returns an error. -func SetFileLabel(path string, label string) error { - return lsetxattr(path, xattrNameSelinux, []byte(label), 0) -} - -// Filecon returns the SELinux label for this path or returns an error. -func FileLabel(path string) (string, error) { - label, err := lgetxattr(path, xattrNameSelinux) - if err != nil { - return "", err - } - // Trim the NUL byte at the end of the byte buffer, if present. - if len(label) > 0 && label[len(label)-1] == '\x00' { - label = label[:len(label)-1] - } - return string(label), nil -} - -/* -SetFSCreateLabel tells kernel the label to create all file system objects -created by this task. Setting label="" to return to default. -*/ -func SetFSCreateLabel(label string) error { - return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid()), label) -} - -/* -FSCreateLabel returns the default label the kernel which the kernel is using -for file system objects created by this task. "" indicates default. -*/ -func FSCreateLabel() (string, error) { - return readCon(fmt.Sprintf("/proc/self/task/%d/attr/fscreate", syscall.Gettid())) -} - -// CurrentLabel returns the SELinux label of the current process thread, or an error. -func CurrentLabel() (string, error) { - return readCon(fmt.Sprintf("/proc/self/task/%d/attr/current", syscall.Gettid())) -} - -// PidLabel returns the SELinux label of the given pid, or an error. -func PidLabel(pid int) (string, error) { - return readCon(fmt.Sprintf("/proc/%d/attr/current", pid)) -} - -/* -ExecLabel returns the SELinux label that the kernel will use for any programs -that are executed by the current process thread, or an error. -*/ -func ExecLabel() (string, error) { - return readCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid())) -} - -func writeCon(name string, val string) error { - out, err := os.OpenFile(name, os.O_WRONLY, 0) - if err != nil { - return err - } - defer out.Close() - - if val != "" { - _, err = out.Write([]byte(val)) - } else { - _, err = out.Write(nil) - } - return err -} - -/* -SetExecLabel sets the SELinux label that the kernel will use for any programs -that are executed by the current process thread, or an error. -*/ -func SetExecLabel(label string) error { - return writeCon(fmt.Sprintf("/proc/self/task/%d/attr/exec", syscall.Gettid()), label) -} - -// Get returns the Context as a string -func (c Context) Get() string { - return fmt.Sprintf("%s:%s:%s:%s", c["user"], c["role"], c["type"], c["level"]) -} - -// NewContext creates a new Context struct from the specified label -func NewContext(label string) Context { - c := make(Context) - - if len(label) != 0 { - con := strings.SplitN(label, ":", 4) - c["user"] = con[0] - c["role"] = con[1] - c["type"] = con[2] - c["level"] = con[3] - } - return c -} - -// ReserveLabel reserves the MLS/MCS level component of the specified label -func ReserveLabel(label string) { - if len(label) != 0 { - con := strings.SplitN(label, ":", 4) - mcsAdd(con[3]) - } -} - -func selinuxEnforcePath() string { - return fmt.Sprintf("%s/enforce", selinuxPath) -} - -// EnforceMode returns the current SELinux mode Enforcing, Permissive, Disabled -func EnforceMode() int { - var enforce int - - enforceS, err := readCon(selinuxEnforcePath()) - if err != nil { - return -1 - } - - enforce, err = strconv.Atoi(string(enforceS)) - if err != nil { - return -1 - } - return enforce -} - -/* -SetEnforce sets the current SELinux mode Enforcing, Permissive. -Disabled is not valid, since this needs to be set at boot time. -*/ -func SetEnforceMode(mode int) error { - return writeCon(selinuxEnforcePath(), fmt.Sprintf("%d", mode)) -} - -/* -DefaultEnforceMode returns the systems default SELinux mode Enforcing, -Permissive or Disabled. Note this is is just the default at boot time. -EnforceMode tells you the systems current mode. -*/ -func DefaultEnforceMode() int { - switch readConfig(selinuxTag) { - case "enforcing": - return Enforcing - case "permissive": - return Permissive - } - return Disabled -} - -func mcsAdd(mcs string) error { - state.Lock() - defer state.Unlock() - if state.mcsList[mcs] { - return fmt.Errorf("MCS Label already exists") - } - state.mcsList[mcs] = true - return nil -} - -func mcsDelete(mcs string) { - state.Lock() - defer state.Unlock() - state.mcsList[mcs] = false -} - -func intToMcs(id int, catRange uint32) string { - var ( - SETSIZE = int(catRange) - TIER = SETSIZE - ORD = id - ) - - if id < 1 || id > 523776 { - return "" - } - - for ORD > TIER { - ORD = ORD - TIER - TIER-- - } - TIER = SETSIZE - TIER - ORD = ORD + TIER - return fmt.Sprintf("s0:c%d,c%d", TIER, ORD) -} - -func uniqMcs(catRange uint32) string { - var ( - n uint32 - c1, c2 uint32 - mcs string - ) - - for { - binary.Read(rand.Reader, binary.LittleEndian, &n) - c1 = n % catRange - binary.Read(rand.Reader, binary.LittleEndian, &n) - c2 = n % catRange - if c1 == c2 { - continue - } else { - if c1 > c2 { - c1, c2 = c2, c1 - } - } - mcs = fmt.Sprintf("s0:c%d,c%d", c1, c2) - if err := mcsAdd(mcs); err != nil { - continue - } - break - } - return mcs -} - -/* -ReleaseLabel will unreserve the MLS/MCS Level field of the specified label. -Allowing it to be used by another process. -*/ -func ReleaseLabel(label string) { - if len(label) != 0 { - con := strings.SplitN(label, ":", 4) - mcsDelete(con[3]) - } -} - -var roFileLabel string - -// ROFileLabel returns the specified SELinux readonly file label -func ROFileLabel() (fileLabel string) { - return roFileLabel -} - -/* -ContainerLabels returns an allocated processLabel and fileLabel to be used for -container labeling by the calling process. -*/ -func ContainerLabels() (processLabel string, fileLabel string) { - var ( - val, key string - bufin *bufio.Reader - ) - - if !GetEnabled() { - return "", "" - } - lxcPath := fmt.Sprintf("%s/contexts/lxc_contexts", getSELinuxPolicyRoot()) - in, err := os.Open(lxcPath) - if err != nil { - return "", "" - } - defer in.Close() - - bufin = bufio.NewReader(in) - - for done := false; !done; { - var line string - if line, err = bufin.ReadString('\n'); err != nil { - if err == io.EOF { - done = true - } else { - goto exit - } - } - line = strings.TrimSpace(line) - if len(line) == 0 { - // Skip blank lines - continue - } - if line[0] == ';' || line[0] == '#' { - // Skip comments - continue - } - if groups := assignRegex.FindStringSubmatch(line); groups != nil { - key, val = strings.TrimSpace(groups[1]), strings.TrimSpace(groups[2]) - if key == "process" { - processLabel = strings.Trim(val, "\"") - } - if key == "file" { - fileLabel = strings.Trim(val, "\"") - } - if key == "ro_file" { - roFileLabel = strings.Trim(val, "\"") - } - } - } - - if processLabel == "" || fileLabel == "" { - return "", "" - } - - if roFileLabel == "" { - roFileLabel = fileLabel - } -exit: - mcs := uniqMcs(1024) - scon := NewContext(processLabel) - scon["level"] = mcs - processLabel = scon.Get() - scon = NewContext(fileLabel) - scon["level"] = mcs - fileLabel = scon.Get() - return processLabel, fileLabel -} - -// SecurityCheckContext validates that the SELinux label is understood by the kernel -func SecurityCheckContext(val string) error { - return writeCon(fmt.Sprintf("%s.context", selinuxPath), val) -} - -/* -CopyLevel returns a label with the MLS/MCS level from src label replaces on -the dest label. -*/ -func CopyLevel(src, dest string) (string, error) { - if src == "" { - return "", nil - } - if err := SecurityCheckContext(src); err != nil { - return "", err - } - if err := SecurityCheckContext(dest); err != nil { - return "", err - } - scon := NewContext(src) - tcon := NewContext(dest) - mcsDelete(tcon["level"]) - mcsAdd(scon["level"]) - tcon["level"] = scon["level"] - return tcon.Get(), nil -} - -// Prevent users from relabing system files -func badPrefix(fpath string) error { - var badprefixes = []string{"/usr"} - - for _, prefix := range badprefixes { - if fpath == prefix || strings.HasPrefix(fpath, fmt.Sprintf("%s/", prefix)) { - return fmt.Errorf("relabeling content in %s is not allowed", prefix) - } - } - return nil -} - -// Chcon changes the fpath file object to the SELinux label label. -// If the fpath is a directory and recurse is true Chcon will walk the -// directory tree setting the label -func Chcon(fpath string, label string, recurse bool) error { - if label == "" { - return nil - } - if err := badPrefix(fpath); err != nil { - return err - } - callback := func(p string, info os.FileInfo, err error) error { - return SetFileLabel(p, label) - } - - if recurse { - return filepath.Walk(fpath, callback) - } - - return SetFileLabel(fpath, label) -} - -// DupSecOpt takes an SELinux process label and returns security options that -// can will set the SELinux Type and Level for future container processes -func DupSecOpt(src string) []string { - if src == "" { - return nil - } - con := NewContext(src) - if con["user"] == "" || - con["role"] == "" || - con["type"] == "" || - con["level"] == "" { - return nil - } - return []string{"user:" + con["user"], - "role:" + con["role"], - "type:" + con["type"], - "level:" + con["level"]} -} - -// DisableSecOpt returns a security opt that can be used to disabling SELinux -// labeling support for future container processes -func DisableSecOpt() []string { - return []string{"disable"} -} diff --git a/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go b/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go deleted file mode 100644 index 7f2ef85049..0000000000 --- a/components/cli/vendor/github.com/opencontainers/selinux/go-selinux/xattrs.go +++ /dev/null @@ -1,78 +0,0 @@ -// +build linux - -package selinux - -import ( - "syscall" - "unsafe" -) - -var _zero uintptr - -// Returns a []byte slice if the xattr is set and nil otherwise -// Requires path and its attribute as arguments -func lgetxattr(path string, attr string) ([]byte, error) { - var sz int - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return nil, err - } - attrBytes, err := syscall.BytePtrFromString(attr) - if err != nil { - return nil, err - } - - // Start with a 128 length byte array - sz = 128 - dest := make([]byte, sz) - destBytes := unsafe.Pointer(&dest[0]) - _sz, _, errno := syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0) - - switch { - case errno == syscall.ENODATA: - return nil, errno - case errno == syscall.ENOTSUP: - return nil, errno - case errno == syscall.ERANGE: - // 128 byte array might just not be good enough, - // A dummy buffer is used ``uintptr(0)`` to get real size - // of the xattrs on disk - _sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(unsafe.Pointer(nil)), uintptr(0), 0, 0) - sz = int(_sz) - if sz < 0 { - return nil, errno - } - dest = make([]byte, sz) - destBytes := unsafe.Pointer(&dest[0]) - _sz, _, errno = syscall.Syscall6(syscall.SYS_LGETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(destBytes), uintptr(len(dest)), 0, 0) - if errno != 0 { - return nil, errno - } - case errno != 0: - return nil, errno - } - sz = int(_sz) - return dest[:sz], nil -} - -func lsetxattr(path string, attr string, data []byte, flags int) error { - pathBytes, err := syscall.BytePtrFromString(path) - if err != nil { - return err - } - attrBytes, err := syscall.BytePtrFromString(attr) - if err != nil { - return err - } - var dataBytes unsafe.Pointer - if len(data) > 0 { - dataBytes = unsafe.Pointer(&data[0]) - } else { - dataBytes = unsafe.Pointer(&_zero) - } - _, _, errno := syscall.Syscall6(syscall.SYS_LSETXATTR, uintptr(unsafe.Pointer(pathBytes)), uintptr(unsafe.Pointer(attrBytes)), uintptr(dataBytes), uintptr(len(data)), uintptr(flags), 0) - if errno != 0 { - return errno - } - return nil -}

Mount path inside the container, for example /some/path/in/container/. If the path does not exist in the container's filesystem, the Engine creates - a directory at the specified location before mounting the volume or bind-mount.

+ a directory at the specified location before mounting the volume or bind mount.