Merge component 'cli' from git@github.com:docker/cli master

This commit is contained in:
Andrew Hsu
2017-09-19 10:12:43 -07:00
500 changed files with 59378 additions and 29888 deletions

View File

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

View File

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

View File

@ -1 +1 @@
17.08.0-dev
17.10.0-dev

View File

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

View File

@ -4,7 +4,6 @@ import (
"fmt"
"io"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/config/configfile"
@ -13,6 +12,7 @@ import (
apiclient "github.com/docker/docker/client"
"github.com/docker/docker/pkg/promise"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)

View File

@ -6,12 +6,12 @@ import (
"runtime"
"sync"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
)

View File

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

View File

@ -10,7 +10,6 @@ import (
"strings"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
@ -20,6 +19,7 @@ import (
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"golang.org/x/net/context"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,6 @@ import (
"regexp"
"runtime"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image/build"
@ -22,12 +21,14 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/urlutil"
units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
@ -243,6 +244,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
ExcludePatterns: excludes,
ChownOpts: &idtools.IDPair{UID: 0, GID: 0},
})
if err != nil {
return err

View File

@ -17,9 +17,9 @@ import (
"github.com/docker/cli/cli/command/image/build"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client/session"
"github.com/docker/docker/client/session/filesync"
"github.com/docker/docker/pkg/progress"
"github.com/moby/buildkit/session"
"github.com/moby/buildkit/session/filesync"
"github.com/pkg/errors"
"golang.org/x/time/rate"
)

View File

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

View File

@ -8,7 +8,6 @@ import (
"path"
"sort"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
"github.com/docker/distribution/reference"
@ -19,6 +18,7 @@ import (
"github.com/docker/notary/tuf/data"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -63,6 +63,7 @@ func TestSecretList(t *testing.T) {
SecretVersion(swarm.Version{Index: 11}),
SecretCreatedAt(time.Now().Add(-2*time.Hour)),
SecretUpdatedAt(time.Now().Add(-1*time.Hour)),
SecretDriver("driver"),
),
}, nil
},

View File

@ -1,6 +1,7 @@
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
Driver: driver
Created at: 0001-01-01 00:00:00 +0000 utc
Updated at: 0001-01-01 00:00:00 +0000 utc

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,6 @@ package service
import (
"encoding/hex"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/trust"
"github.com/docker/distribution/reference"
@ -12,6 +11,7 @@ import (
"github.com/docker/notary/tuf/data"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -128,7 +128,7 @@ func Service(
Labels: AddStackLabel(namespace, service.Deploy.Labels),
},
TaskTemplate: swarm.TaskSpec{
ContainerSpec: swarm.ContainerSpec{
ContainerSpec: &swarm.ContainerSpec{
Image: service.Image,
Command: service.Entrypoint,
Args: service.Command,
@ -295,7 +295,13 @@ func convertServiceSecrets(
})
}
return servicecli.ParseSecrets(client, refs)
secrs, err := servicecli.ParseSecrets(client, refs)
if err != nil {
return nil, err
}
// sort to ensure idempotence (don't restart services just because the entries are in different order)
sort.SliceStable(secrs, func(i, j int) bool { return secrs[i].SecretName < secrs[j].SecretName })
return secrs, err
}
// TODO: fix configs API so that ConfigsAPIClient is not required here
@ -346,7 +352,13 @@ func convertServiceConfigObjs(
})
}
return servicecli.ParseConfigs(client, refs)
confs, err := servicecli.ParseConfigs(client, refs)
if err != nil {
return nil, err
}
// sort to ensure idempotence (don't restart services just because the entries are in different order)
sort.SliceStable(confs, func(i, j int) bool { return confs[i].ConfigName < confs[j].ConfigName })
return confs, err
}
func uint32Ptr(value uint32) *uint32 {

View File

@ -2,6 +2,21 @@ version: "3.4"
services:
foo:
build:
context: ./dir
dockerfile: Dockerfile
args:
foo: bar
target: foo
network: foo
cache_from:
- foo
- bar
labels: [FOO=BAR]
cap_add:
- ALL

View File

@ -8,7 +8,6 @@ import (
"sort"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/compose/interpolation"
"github.com/docker/cli/cli/compose/schema"
"github.com/docker/cli/cli/compose/template"
@ -19,6 +18,7 @@ import (
shellwords "github.com/mattn/go-shellwords"
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
yaml "gopkg.in/yaml.v2"
)

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/cli/cli/compose/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func buildConfigDetails(source map[string]interface{}, env map[string]string) types.ConfigDetails {
@ -164,17 +165,13 @@ var sampleConfig = types.Config{
func TestParseYAML(t *testing.T) {
dict, err := ParseYAML([]byte(sampleYAML))
if !assert.NoError(t, err) {
return
}
require.NoError(t, err)
assert.Equal(t, sampleDict, dict)
}
func TestLoad(t *testing.T) {
actual, err := Load(buildConfigDetails(sampleDict, nil))
if !assert.NoError(t, err) {
return
}
require.NoError(t, err)
assert.Equal(t, serviceSort(sampleConfig.Services), serviceSort(actual.Services))
assert.Equal(t, sampleConfig.Networks, actual.Networks)
assert.Equal(t, sampleConfig.Volumes, actual.Volumes)
@ -191,11 +188,9 @@ secrets:
super:
external: true
`)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, len(actual.Services), 1)
assert.Equal(t, len(actual.Secrets), 1)
require.NoError(t, err)
assert.Len(t, actual.Services, 1)
assert.Len(t, actual.Secrets, 1)
}
func TestLoadV33(t *testing.T) {
@ -211,19 +206,15 @@ configs:
super:
external: true
`)
if !assert.NoError(t, err) {
return
}
assert.Equal(t, len(actual.Services), 1)
require.NoError(t, err)
require.Len(t, actual.Services, 1)
assert.Equal(t, actual.Services[0].CredentialSpec.File, "/foo")
assert.Equal(t, len(actual.Configs), 1)
require.Len(t, actual.Configs, 1)
}
func TestParseAndLoad(t *testing.T) {
actual, err := loadYAML(sampleYAML)
if !assert.NoError(t, err) {
return
}
require.NoError(t, err)
assert.Equal(t, serviceSort(sampleConfig.Services), serviceSort(actual.Services))
assert.Equal(t, sampleConfig.Networks, actual.Networks)
assert.Equal(t, sampleConfig.Volumes, actual.Volumes)
@ -231,15 +222,15 @@ func TestParseAndLoad(t *testing.T) {
func TestInvalidTopLevelObjectType(t *testing.T) {
_, err := loadYAML("1")
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "Top-level object must be a mapping")
_, err = loadYAML("\"hello\"")
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "Top-level object must be a mapping")
_, err = loadYAML("[\"hello\"]")
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "Top-level object must be a mapping")
}
@ -250,7 +241,7 @@ version: "3"
foo:
image: busybox
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "Non-string key at top level: 123")
_, err = loadYAML(`
@ -261,7 +252,7 @@ services:
123:
image: busybox
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "Non-string key in services: 123")
_, err = loadYAML(`
@ -275,7 +266,7 @@ networks:
config:
- 123: oh dear
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "Non-string key in networks.default.ipam.config[0]: 123")
_, err = loadYAML(`
@ -286,7 +277,7 @@ services:
environment:
1: FOO
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "Non-string key in services.dict-env.environment: 1")
}
@ -297,7 +288,7 @@ services:
foo:
image: busybox
`)
assert.NoError(t, err)
require.NoError(t, err)
_, err = loadYAML(`
version: "3.0"
@ -305,7 +296,7 @@ services:
foo:
image: busybox
`)
assert.NoError(t, err)
require.NoError(t, err)
}
func TestUnsupportedVersion(t *testing.T) {
@ -315,7 +306,7 @@ services:
foo:
image: busybox
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "version")
_, err = loadYAML(`
@ -324,7 +315,7 @@ services:
foo:
image: busybox
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "version")
}
@ -335,7 +326,7 @@ services:
foo:
image: busybox
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "version must be a string")
}
@ -354,7 +345,7 @@ services:
- foo:
image: busybox
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "services must be a mapping")
_, err = loadYAML(`
@ -362,7 +353,7 @@ version: "3"
services:
foo: busybox
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "services.foo must be a mapping")
_, err = loadYAML(`
@ -371,7 +362,7 @@ networks:
- default:
driver: bridge
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "networks must be a mapping")
_, err = loadYAML(`
@ -379,7 +370,7 @@ version: "3"
networks:
default: bridge
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "networks.default must be a mapping")
_, err = loadYAML(`
@ -388,7 +379,7 @@ volumes:
- data:
driver: local
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "volumes must be a mapping")
_, err = loadYAML(`
@ -396,7 +387,7 @@ version: "3"
volumes:
data: local
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "volumes.data must be a mapping")
}
@ -407,7 +398,7 @@ services:
foo:
image: ["busybox", "latest"]
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "services.foo.image must be a string")
}
@ -458,7 +449,7 @@ services:
environment:
FOO: ["1"]
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "services.dict-env.environment.FOO must be a string, number or null")
}
@ -470,7 +461,7 @@ services:
image: busybox
environment: "FOO=1"
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "services.dict-env.environment must be a mapping")
}
@ -497,7 +488,7 @@ volumes:
"FOO": "foo",
})
assert.NoError(t, err)
require.NoError(t, err)
expectedLabels := types.Labels{
"home1": home,
@ -517,19 +508,21 @@ version: "3"
services:
web:
image: web
build: ./web
build:
context: ./web
links:
- bar
db:
image: db
build: ./db
build:
context: ./db
`))
assert.NoError(t, err)
require.NoError(t, err)
configDetails := buildConfigDetails(dict, nil)
_, err = Load(configDetails)
assert.NoError(t, err)
require.NoError(t, err)
unsupported := GetUnsupportedProperties(configDetails)
assert.Equal(t, []string{"build", "links"}, unsupported)
@ -547,15 +540,15 @@ services:
container_name: db
expose: ["5434"]
`))
assert.NoError(t, err)
require.NoError(t, err)
configDetails := buildConfigDetails(dict, nil)
_, err = Load(configDetails)
assert.NoError(t, err)
require.NoError(t, err)
deprecated := GetDeprecatedProperties(configDetails)
assert.Equal(t, 2, len(deprecated))
assert.Len(t, deprecated, 2)
assert.Contains(t, deprecated, "container_name")
assert.Contains(t, deprecated, "expose")
}
@ -574,12 +567,12 @@ services:
service: foo
`)
assert.Error(t, err)
require.Error(t, err)
assert.IsType(t, &ForbiddenPropertiesError{}, err)
fmt.Println(err)
forbidden := err.(*ForbiddenPropertiesError).Properties
assert.Equal(t, 2, len(forbidden))
assert.Len(t, forbidden, 2)
assert.Contains(t, forbidden, "volume_driver")
assert.Contains(t, forbidden, "extends")
}
@ -595,7 +588,7 @@ func TestInvalidResource(t *testing.T) {
impossible:
x: 1
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "Additional property impossible is not allowed")
}
@ -608,7 +601,7 @@ volumes:
driver: foobar
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "conflicting parameters \"external\" and \"driver\" specified for volume")
assert.Contains(t, err.Error(), "external_volume")
}
@ -623,7 +616,7 @@ volumes:
beep: boop
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "conflicting parameters \"external\" and \"driver_opts\" specified for volume")
assert.Contains(t, err.Error(), "external_volume")
}
@ -638,7 +631,7 @@ volumes:
- beep=boop
`)
assert.Error(t, err)
require.Error(t, err)
assert.Contains(t, err.Error(), "conflicting parameters \"external\" and \"labels\" specified for volume")
assert.Contains(t, err.Error(), "external_volume")
}
@ -653,8 +646,7 @@ volumes:
name: external_name
`)
assert.Error(t, err)
fmt.Println(err)
require.Error(t, err)
assert.Contains(t, err.Error(), "volume.external.name and volume.name conflict; only use volume.name")
assert.Contains(t, err.Error(), "external_volume")
}
@ -669,23 +661,30 @@ func uint64Ptr(value uint64) *uint64 {
func TestFullExample(t *testing.T) {
bytes, err := ioutil.ReadFile("full-example.yml")
assert.NoError(t, err)
require.NoError(t, err)
homeDir := "/home/foo"
env := map[string]string{"HOME": homeDir, "QUX": "qux_from_environment"}
config, err := loadYAMLWithEnv(string(bytes), env)
if !assert.NoError(t, err) {
return
}
require.NoError(t, err)
workingDir, err := os.Getwd()
assert.NoError(t, err)
require.NoError(t, err)
stopGracePeriod := time.Duration(20 * time.Second)
expectedServiceConfig := types.ServiceConfig{
Name: "foo",
Build: types.BuildConfig{
Context: "./dir",
Dockerfile: "Dockerfile",
Args: map[string]*string{"foo": strPtr("bar")},
Target: "foo",
Network: "foo",
CacheFrom: []string{"foo", "bar"},
Labels: map[string]string{"FOO": "BAR"},
},
CapAdd: []string{"ALL"},
CapDrop: []string{"NET_ADMIN", "SYS_ADMIN"},
CgroupParent: "m-executor-abcd",
@ -1069,9 +1068,7 @@ networks:
mynet2:
driver: bridge
`)
if !assert.NoError(t, err) {
return
}
require.NoError(t, err)
expected := map[string]types.NetworkConfig{
"mynet1": {
@ -1105,9 +1102,7 @@ services:
target: 22
published: 10022
`)
if !assert.NoError(t, err) {
return
}
require.NoError(t, err)
expected := []types.ServicePortConfig{
{
@ -1170,7 +1165,7 @@ services:
},
}
assert.Equal(t, 1, len(config.Services))
assert.Len(t, config.Services, 1)
assert.Equal(t, expected, config.Services[0].Ports)
}
@ -1188,9 +1183,7 @@ services:
volumes:
foo: {}
`)
if !assert.NoError(t, err) {
return
}
require.NoError(t, err)
expected := types.ServiceVolumeConfig{
Type: "volume",
@ -1199,7 +1192,7 @@ volumes:
ReadOnly: true,
}
assert.Equal(t, 1, len(config.Services))
assert.Equal(t, 1, len(config.Services[0].Volumes))
require.Len(t, config.Services, 1)
assert.Len(t, config.Services[0].Volumes, 1)
assert.Equal(t, expected, config.Services[0].Volumes[0])
}

View File

@ -79,6 +79,7 @@ type Config struct {
type ServiceConfig struct {
Name string
Build BuildConfig
CapAdd []string `mapstructure:"cap_add"`
CapDrop []string `mapstructure:"cap_drop"`
CgroupParent string `mapstructure:"cgroup_parent"`
@ -126,6 +127,18 @@ type ServiceConfig struct {
WorkingDir string `mapstructure:"working_dir"`
}
// BuildConfig is a type for build
// using the same format at libcompose: https://github.com/docker/libcompose/blob/master/yaml/build.go#L12
type BuildConfig struct {
Context string
Dockerfile string
Args MappingWithEquals
Labels Labels
CacheFrom StringList `mapstructure:"cache_from"`
Network string
Target string
}
// ShellCommand is a string or list of string args
type ShellCommand []string

View File

@ -3,7 +3,7 @@ package debug
import (
"os"
"github.com/Sirupsen/logrus"
"github.com/sirupsen/logrus"
)
// Enable sets the DEBUG env var to true

View File

@ -4,7 +4,7 @@ import (
"os"
"testing"
"github.com/Sirupsen/logrus"
"github.com/sirupsen/logrus"
)
func TestEnable(t *testing.T) {

View File

@ -5,10 +5,10 @@ import (
"os"
"path/filepath"
"github.com/Sirupsen/logrus"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/opts"
"github.com/docker/go-connections/tlsconfig"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
)

View File

@ -10,7 +10,6 @@ import (
"path/filepath"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/distribution/registry/client/auth"
@ -29,6 +28,7 @@ import (
"github.com/docker/notary/tuf/data"
"github.com/docker/notary/tuf/signed"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
var (

View File

@ -6,7 +6,6 @@ import (
"os"
"strings"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/commands"
@ -16,6 +15,7 @@ import (
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/term"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

View File

@ -5,9 +5,9 @@ import (
"os"
"testing"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/debug"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
)

View File

@ -441,18 +441,23 @@ __docker_complete_stacks() {
# precedence over the environment setting.
# Completions may be added with `--add`, e.g. `--add self`.
__docker_nodes() {
local format
if [ "$DOCKER_COMPLETION_SHOW_NODE_IDS" = yes ] ; then
format='{{.ID}} {{.Hostname}}'
else
format='{{.Hostname}}'
fi
local add=()
local fields='$2' # default: node name only
[ "${DOCKER_COMPLETION_SHOW_NODE_IDS}" = yes ] && fields='$1,$2' # ID and name
while true ; do
case "$1" in
--id)
fields='$1' # IDs only
format='{{.ID}}'
shift
;;
--name)
fields='$2' # names only
format='{{.Hostname}}'
shift
;;
--add)
@ -465,7 +470,7 @@ __docker_nodes() {
esac
done
echo "$(__docker_q node ls "$@" | tr -d '*' | awk "NR>1 {print $fields}")" "${add[@]}"
echo "$(__docker_q node ls --format "$format" "$@")" "${add[@]}"
}
# __docker_complete_nodes applies completion of nodes based on the current

View File

@ -24,7 +24,7 @@ see [Feature Deprecation Policy](https://docs.docker.com/engine/#feature-depreca
**Deprecated In Release: v17.05.0**
**Disabled by default in release: v17.09**
**Disabled by default in release: [v17.09](https://github.com/docker/docker-ce/releases/tag/v17.09.0-ce)**
Docker 17.05.0 added an optional `--detach=false` option to make the
`docker service create` and `docker service update` work synchronously. This
@ -315,7 +315,7 @@ Since 1.9, Docker Content Trust Offline key has been renamed to Root key and the
**Deprecated In Release: [v1.6.0](https://github.com/docker/docker/releases/tag/v1.6.0)**
**Target For Removal In Release: v17.09**
**Removed In Release: [v17.09](https://github.com/docker/docker-ce/releases/tag/v17.09.0-ce)**
The flag `--api-enable-cors` is deprecated since v1.6.0. Use the flag
`--api-cors-header` instead.

View File

@ -248,7 +248,7 @@ $ docker -H tcp://127.0.0.1:2375 pull ubuntu
### Daemon storage-driver
The Docker daemon has support for several different image layer storage
On Linux, the Docker daemon has support for several different image layer storage
drivers: `aufs`, `devicemapper`, `btrfs`, `zfs`, `overlay` and `overlay2`.
The `aufs` driver is the oldest, but is based on a Linux kernel patch-set that
@ -296,11 +296,16 @@ to use it.
> **Note**: Both `overlay` and `overlay2` are currently unsupported on `btrfs`
> or any Copy on Write filesystem and should only be used over `ext4` partitions.
On Windows, the Docker daemon supports a single image layer storage driver
depending on the image platform: `windowsfilter` for Windows images, and
`lcow` for Linux containers on Windows.
### Options per storage driver
Particular storage-driver can be configured with options specified with
`--storage-opt` flags. Options for `devicemapper` are prefixed with `dm`,
options for `zfs` start with `zfs` and options for `btrfs` start with `btrfs`.
options for `zfs` start with `zfs`, options for `btrfs` start with `btrfs`
and options for `lcow` start with `lcow`.
#### Devicemapper options
@ -780,6 +785,113 @@ conditions the user can pass any size less then the backing fs size.
$ sudo dockerd -s overlay2 --storage-opt overlay2.size=1G
```
#### Windowsfilter options
##### `size`
Specifies the size to use when creating the sandbox which is used for containers.
Defaults to 20G.
###### Example
```PowerShell
C:\> dockerd --storage-opt size=40G
```
#### LCOW (Linux Containers on Windows) options
##### `lcow.globalmode`
Specifies whether the daemon instantiates utility VM instances as required
(recommended and default if omitted), or uses single global utility VM (better
performance, but has security implications and not recommended for production
deployments).
###### Example
```PowerShell
C:\> dockerd --storage-opt lcow.globalmode=false
```
##### `lcow.kirdpath`
Specifies the folder path to the location of a pair of kernel and initrd files
used for booting a utility VM. Defaults to `%ProgramFiles%\Linux Containers`.
###### Example
```PowerShell
C:\> dockerd --storage-opt lcow.kirdpath=c:\path\to\files
```
##### `lcow.kernel`
Specifies the filename of a kernel file located in the `lcow.kirdpath` path.
Defaults to `bootx64.efi`.
###### Example
```PowerShell
C:\> dockerd --storage-opt lcow.kernel=kernel.efi
```
##### `lcow.initrd`
Specifies the filename of an initrd file located in the `lcow.kirdpath` path.
Defaults to `initrd.img`.
###### Example
```PowerShell
C:\> dockerd --storage-opt lcow.initrd=myinitrd.img
```
##### `lcow.bootparameters`
Specifies additional boot parameters for booting utility VMs when in kernel/
initrd mode. Ignored if the utility VM is booting from VHD. These settings
are kernel specific.
###### Example
```PowerShell
C:\> dockerd --storage-opt "lcow.bootparameters='option=value'"
```
##### `lcow.vhdx`
Specifies a custom VHDX to boot a utility VM, as an alternate to kernel
and initrd booting. Defaults to `uvm.vhdx` under `lcow.kirdpath`.
###### Example
```PowerShell
C:\> dockerd --storage-opt lcow.vhdx=custom.vhdx
```
##### `lcow.timeout`
Specifies the timeout for utility VM operations in seconds. Defaults
to 300.
###### Example
```PowerShell
C:\> dockerd --storage-opt lcow.timeout=240
```
##### `lcow.sandboxsize`
Specifies the size in GB to use when creating the sandbox which is used for
containers. Defaults to 20. Cannot be less than 20.
###### Example
```PowerShell
C:\> dockerd --storage-opt lcow.sandboxsize=40
```
### Docker runtime execution options
The Docker daemon relies on a

View File

@ -159,7 +159,7 @@ $ docker service create --name redis --secret secret.json redis:3.0.6
4cdgfyky7ozwh3htjfw0d12qv
```
Create a service specifying the secret, target, user/group ID and mode:
Create a service specifying the secret, target, user/group ID, and mode:
```bash
$ docker service create --name redis \
@ -398,7 +398,7 @@ For more information about bind propagation, see the
#### Options for Named Volumes
The following options can only be used for named volumes (`type=volume`);
The following options can only be used for named volumes (`type=volume`):
<table>
@ -419,7 +419,7 @@ The following options can only be used for named volumes (`type=volume`);
<td>
One or more custom metadata ("labels") to apply to the volume upon
creation. For example,
`volume-label=mylabel=hello-world,my-other-label=hello-mars`. For more
<tt>volume-label=mylabel=hello-world,my-other-label=hello-mars</tt>. For more
information about labels, refer to
<a href="https://docs.docker.com/engine/userguide/labels-custom-metadata/">apply custom metadata</a>.
</td>
@ -430,8 +430,8 @@ The following options can only be used for named volumes (`type=volume`);
By default, if you attach an empty volume to a container, and files or
directories already existed at the mount-path in the container (<tt>dst</tt>),
the Engine copies those files and directories into the volume, allowing
the host to access them. Set `volume-nocopy` to disables copying files
from the container's filesystem to the volume and mount the empty volume.
the host to access them. Set <tt>volume-nocopy</tt> to disable copying files
from the container's filesystem to the volume and mount the empty volume.<br />
A value is optional:
@ -833,6 +833,10 @@ Valid placeholders for the Go template are listed below:
<td><tt>.Node.ID</tt></td>
<td>Node ID</td>
</tr>
<tr>
<td><tt>.Node.Hostname</tt></td>
<td>Node Hostname</td>
</tr>
<tr>
<td><tt>.Task.ID</tt></td>
<td>Task ID</td>
@ -851,11 +855,11 @@ Valid placeholders for the Go template are listed below:
#### Template example
In this example, we are going to set the template of the created containers based on the
service's name and the node's ID where it sits.
service's name, the node's ID and hostname where it sits.
```bash
$ docker service create --name hosttempl \
--hostname="{{.Node.ID}}-{{.Service.Name}}"\
--hostname="{{.Node.Hostname}}-{{.Node.ID}}-{{.Service.Name}}"\
busybox top
va8ew30grofhjoychbr6iot8c
@ -865,7 +869,7 @@ $ docker service ps va8ew30grofhjoychbr6iot8c
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
wo41w8hg8qan hosttempl.1 busybox:latest@sha256:29f5d56d12684887bdfa50dcd29fc31eea4aaf4ad3bec43daf19026a7ce69912 2e7a8a9c4da2 Running Running about a minute ago
$ docker inspect --format="{{.Config.Hostname}}" hosttempl.1.wo41w8hg8qanxwjwsg4kxpprj
$ docker inspect --format="{{.Config.Hostname}}" 2e7a8a9c4da2-wo41w8hg8qanxwjwsg4kxpprj-hosttempl
x3ti0erg11rjpg64m75kej2mz-hosttempl
```

View File

@ -18,34 +18,72 @@ keywords: "system, prune, delete, remove"
```markdown
Usage: docker system prune [OPTIONS]
Delete unused data
Remove unused data
Options:
-a, --all Remove all unused images not just dangling ones
--filter filter Provide filter values (e.g. 'until=<timestamp>')
--filter filter Provide filter values (e.g. 'label=<key>=<value>')
-f, --force Do not prompt for confirmation
--help Print usage
--volumes Prune volumes
```
## Description
Remove all unused containers, volumes, networks and images (both dangling and unreferenced).
Remove all unused containers, networks, images (both dangling and unreferenced),
and optionally, volumes.
## Examples
```bash
$ docker system prune -a
$ docker system prune
WARNING! This will remove:
- all stopped containers
- all volumes not used by at least one container
- all networks not used by at least one container
- all images without at least one container associated to them
- all stopped containers
- all networks not used by at least one container
- all dangling images
- all build cache
Are you sure you want to continue? [y/N] y
Deleted Containers:
f44f9b81948b3919590d5f79a680d8378f1139b41952e219830a33027c80c867
792776e68ac9d75bce4092bc1b5cc17b779bc926ab04f4185aec9bf1c0d4641f
Deleted Networks:
network1
network2
Deleted Images:
untagged: hello-world@sha256:f3b3b28a45160805bb16542c9531888519430e9e6d6ffc09d72261b0d26ff74f
deleted: sha256:1815c82652c03bfd8644afda26fb184f2ed891d921b20a0703b46768f9755c57
deleted: sha256:45761469c965421a92a69cc50e92c01e0cfa94fe026cdd1233445ea00e96289a
Total reclaimed space: 1.84kB
```
By default, volumes are not removed to prevent important data from being
deleted if there is currently no container using the volume. Use the `--volumes`
flag when running the command to prune volumes as well:
```bash
$ docker system prune -a --volumes
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all volumes not used by at least one container
- all images without at least one container associated to them
- all build cache
Are you sure you want to continue? [y/N] y
Deleted Containers:
0998aa37185a1a7036b0e12cf1ac1b6442dcfa30a5c9650a42ed5010046f195b
73958bfb884fa81fa4cc6baf61055667e940ea2357b4036acbbe25a60f442a4d
Deleted Networks:
my-network-a
my-network-b
Deleted Volumes:
named-vol
@ -68,6 +106,13 @@ deleted: sha256:3a88a5c81eb5c283e72db2dbc6d65cbfd8e80b6c89bb6e714cfaaa0eed99c548
Total reclaimed space: 13.5 MB
```
> **Note**: The `--volumes` option was added in Docker 17.06.1. Older versions
> of Docker prune volumes by default, along with other Docker objects. On older
> versions, run `docker container prune`, `docker network prune`, and
> `docker image prune` separately to remove unused containers, networks, and
> images, without removing volumes.
### Filtering
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more

View File

@ -279,7 +279,7 @@ The following values are accepted:
| "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.
or `"shareable"`, depending on the daemon version and configuration.
IPC (POSIX/SysV IPC) namespace provides separation of named shared memory
segments, semaphores and message queues.

View File

@ -14,10 +14,14 @@ import (
)
type cmdOption struct {
Option string
Shorthand string `yaml:",omitempty"`
DefaultValue string `yaml:"default_value,omitempty"`
Description string `yaml:",omitempty"`
Option string
Shorthand string `yaml:",omitempty"`
ValueType string `yaml:"value_type,omitempty"`
DefaultValue string `yaml:"default_value,omitempty"`
Description string `yaml:",omitempty"`
Deprecated bool
MinAPIVersion string `yaml:"min_api_version,omitempty"`
Experimental bool
}
type cmdDoc struct {
@ -35,6 +39,9 @@ type cmdDoc struct {
Options []cmdOption `yaml:",omitempty"`
InheritedOptions []cmdOption `yaml:"inherited_options,omitempty"`
Example string `yaml:"examples,omitempty"`
Deprecated bool
MinAPIVersion string `yaml:"min_api_version,omitempty"`
Experimental bool
}
// GenYamlTree creates yaml structured ref files
@ -73,12 +80,11 @@ func GenYamlTreeCustom(cmd *cobra.Command, dir string, filePrepender, linkHandle
}
// GenYamlCustom creates custom yaml output
// nolint: gocyclo
func GenYamlCustom(cmd *cobra.Command, w io.Writer) error {
cliDoc := cmdDoc{}
cliDoc.Name = cmd.CommandPath()
// Check experimental: ok := cmd.Tags["experimental"]
cliDoc.Aliases = strings.Join(cmd.Aliases, ", ")
cliDoc.Short = cmd.Short
cliDoc.Long = cmd.Long
@ -93,6 +99,18 @@ func GenYamlCustom(cmd *cobra.Command, w io.Writer) error {
if len(cmd.Example) > 0 {
cliDoc.Example = cmd.Example
}
if len(cmd.Deprecated) > 0 {
cliDoc.Deprecated = true
}
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
for curr := cmd; curr != nil; curr = curr.Parent() {
if v, ok := curr.Tags["version"]; ok && cliDoc.MinAPIVersion == "" {
cliDoc.MinAPIVersion = v
}
if _, ok := curr.Tags["experimental"]; ok && !cliDoc.Experimental {
cliDoc.Experimental = true
}
}
flags := cmd.NonInheritedFlags()
if flags.HasFlags() {
@ -142,28 +160,34 @@ func GenYamlCustom(cmd *cobra.Command, w io.Writer) error {
}
func genFlagResult(flags *pflag.FlagSet) []cmdOption {
var result []cmdOption
var (
result []cmdOption
opt cmdOption
)
flags.VisitAll(func(flag *pflag.Flag) {
opt = cmdOption{
Option: flag.Name,
ValueType: flag.Value.Type(),
DefaultValue: forceMultiLine(flag.DefValue),
Description: forceMultiLine(flag.Usage),
Deprecated: len(flag.Deprecated) > 0,
}
// Todo, when we mark a shorthand is deprecated, but specify an empty message.
// The flag.ShorthandDeprecated is empty as the shorthand is deprecated.
// Using len(flag.ShorthandDeprecated) > 0 can't handle this, others are ok.
if !(len(flag.ShorthandDeprecated) > 0) && len(flag.Shorthand) > 0 {
opt := cmdOption{
Option: flag.Name,
Shorthand: flag.Shorthand,
DefaultValue: flag.DefValue,
Description: forceMultiLine(flag.Usage),
}
result = append(result, opt)
} else {
opt := cmdOption{
Option: flag.Name,
DefaultValue: forceMultiLine(flag.DefValue),
Description: forceMultiLine(flag.Usage),
}
result = append(result, opt)
opt.Shorthand = flag.Shorthand
}
if _, ok := flag.Annotations["experimental"]; ok {
opt.Experimental = true
}
if v, ok := flag.Annotations["version"]; ok {
opt.MinAPIVersion = v[0]
}
result = append(result, opt)
})
return result

View File

@ -0,0 +1,17 @@
package container
import (
"fmt"
"os"
"testing"
"github.com/docker/cli/internal/test/environment"
)
func TestMain(m *testing.M) {
if err := environment.Setup(); err != nil {
fmt.Println(err.Error())
os.Exit(3)
}
os.Exit(m.Run())
}

View File

@ -0,0 +1,42 @@
package container
import (
"fmt"
"testing"
shlex "github.com/flynn-archive/go-shlex"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/gotestyourself/gotestyourself/icmd"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const alpineImage = "registry:5000/alpine:3.6"
func TestRunAttachedFromRemoteImageAndRemove(t *testing.T) {
image := createRemoteImage(t)
result := icmd.RunCmd(shell(t,
"docker run --rm %s echo this is output", image))
result.Assert(t, icmd.Success)
assert.Equal(t, "this is output\n", result.Stdout())
golden.Assert(t, result.Stderr(), "run-attached-from-remote-and-remove.golden")
}
// TODO: create this with registry API instead of engine API
func createRemoteImage(t *testing.T) string {
image := "registry:5000/alpine:test-run-pulls"
icmd.RunCommand("docker", "pull", alpineImage).Assert(t, icmd.Success)
icmd.RunCommand("docker", "tag", alpineImage, image).Assert(t, icmd.Success)
icmd.RunCommand("docker", "push", image).Assert(t, icmd.Success)
icmd.RunCommand("docker", "rmi", image).Assert(t, icmd.Success)
return image
}
// 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}
}

View File

@ -0,0 +1,4 @@
Unable to find image 'registry:5000/alpine:test-run-pulls' locally
test-run-pulls: Pulling from alpine
Digest: sha256:641b95ddb2ea9dc2af1a0113b6b348ebc20872ba615204fbe12148e98fd6f23d
Status: Downloaded newer image for registry:5000/alpine:test-run-pulls

View File

@ -5,22 +5,13 @@ import (
"os"
"testing"
"github.com/pkg/errors"
"github.com/docker/cli/internal/test/environment"
)
func TestMain(m *testing.M) {
if err := setupTestEnv(); err != nil {
if err := environment.Setup(); 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)
}

View File

@ -4,14 +4,17 @@ import (
"fmt"
"strings"
"testing"
"time"
"github.com/docker/cli/internal/test/environment"
shlex "github.com/flynn-archive/go-shlex"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/gotestyourself/gotestyourself/icmd"
"github.com/gotestyourself/gotestyourself/poll"
"github.com/stretchr/testify/require"
)
var pollSettings = environment.DefaultPollSettings
func TestRemove(t *testing.T) {
stackname := "test-stack-remove"
deployFullStack(t, stackname)
@ -19,8 +22,8 @@ func TestRemove(t *testing.T) {
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")
result.Assert(t, icmd.Expected{Err: icmd.None})
golden.Assert(t, result.Stdout(), "stack-remove-success.golden")
}
func deployFullStack(t *testing.T, stackname string) {
@ -29,21 +32,24 @@ func deployFullStack(t *testing.T, stackname string) {
"docker stack deploy --compose-file=./testdata/full-stack.yml %s", stackname))
result.Assert(t, icmd.Success)
waitOn(t, taskCount(stackname, 2), 0)
poll.WaitOn(t, taskCount(stackname, 2), pollSettings)
}
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)
poll.WaitOn(t, taskCount(stackname, 0), pollSettings)
}
func taskCount(stackname string, expected int) func() (bool, error) {
return func() (bool, error) {
func taskCount(stackname string, expected int) func(t poll.LogT) poll.Result {
return func(poll.LogT) poll.Result {
result := icmd.RunCommand(
"docker", "stack", "ps", "-f=desired-state=running", stackname)
count := lines(result.Stdout()) - 1
return count == expected, nil
if count == expected {
return poll.Success()
}
return poll.Continue("task count is %d waiting for %d", count, expected)
}
}
@ -57,33 +63,3 @@ func shell(t *testing.T, format string, args ...interface{}) icmd.Cmd {
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
}

View File

@ -0,0 +1,45 @@
package builders
import (
"github.com/docker/docker/api/types"
)
// NetworkResource creates a network resource with default values.
// Any number of networkResource function builder can be pass to modify the existing value.
// feel free to add another builder func if you need to override another value
func NetworkResource(builders ...func(resource *types.NetworkResource)) *types.NetworkResource {
resource := &types.NetworkResource{}
for _, builder := range builders {
builder(resource)
}
return resource
}
// NetworkResourceName sets the name of the resource network
func NetworkResourceName(name string) func(networkResource *types.NetworkResource) {
return func(networkResource *types.NetworkResource) {
networkResource.Name = name
}
}
// NetworkResourceID sets the ID of the resource network
func NetworkResourceID(id string) func(networkResource *types.NetworkResource) {
return func(networkResource *types.NetworkResource) {
networkResource.ID = id
}
}
// NetworkResourceDriver sets the driver of the resource network
func NetworkResourceDriver(name string) func(networkResource *types.NetworkResource) {
return func(networkResource *types.NetworkResource) {
networkResource.Driver = name
}
}
// NetworkResourceScope sets the Scope of the resource network
func NetworkResourceScope(scope string) func(networkResource *types.NetworkResource) {
return func(networkResource *types.NetworkResource) {
networkResource.Scope = scope
}
}

View File

@ -32,6 +32,15 @@ func SecretName(name string) func(secret *swarm.Secret) {
}
}
// SecretDriver sets the secret's driver name
func SecretDriver(driver string) func(secret *swarm.Secret) {
return func(secret *swarm.Secret) {
secret.Spec.Driver = &swarm.Driver{
Name: driver,
}
}
}
// SecretID sets the secret's ID
func SecretID(ID string) func(secret *swarm.Secret) {
return func(secret *swarm.Secret) {

View File

@ -56,7 +56,7 @@ func ReplicatedService(replicas uint64) func(*swarm.Service) {
// ServiceImage sets the service's image
func ServiceImage(image string) func(*swarm.Service) {
return func(service *swarm.Service) {
service.Spec.TaskTemplate = swarm.TaskSpec{ContainerSpec: swarm.ContainerSpec{Image: image}}
service.Spec.TaskTemplate = swarm.TaskSpec{ContainerSpec: &swarm.ContainerSpec{Image: image}}
}
}

View File

@ -140,7 +140,7 @@ func WithTaskSpec(specBuilders ...func(*swarm.TaskSpec)) func(*swarm.Task) {
// Any number of taskSpec function builder can be pass to augment it.
func TaskSpec(specBuilders ...func(*swarm.TaskSpec)) *swarm.TaskSpec {
taskSpec := &swarm.TaskSpec{
ContainerSpec: swarm.ContainerSpec{
ContainerSpec: &swarm.ContainerSpec{
Image: "myimage:mytag",
},
}

View File

@ -0,0 +1,21 @@
package environment
import (
"os"
"time"
"github.com/gotestyourself/gotestyourself/poll"
"github.com/pkg/errors"
)
// Setup a new environment
func Setup() error {
dockerHost := os.Getenv("TEST_DOCKER_HOST")
if dockerHost == "" {
return errors.New("$TEST_DOCKER_HOST must be set")
}
return os.Setenv("DOCKER_HOST", dockerHost)
}
// DefaultPollSettings used with gotestyourself/poll
var DefaultPollSettings = poll.WithDelay(100 * time.Millisecond)

View File

@ -1,7 +1,7 @@
#!/usr/bin/env bash
set -eu -o pipefail
src=alpine:3.6
src=alpine@sha256:f006ecbb824d87947d0b51ab8488634bf69fe4094959d935c0c103f4820a417d
dest=registry:5000/alpine:3.6
docker pull $src
docker tag $src $dest

View File

@ -39,11 +39,12 @@ function cleanup {
function runtests {
local engine_host=$1
# shellcheck disable=SC2086
env -i \
TEST_DOCKER_HOST="$engine_host" \
GOPATH="$GOPATH" \
PATH="$PWD/build/" \
"$(which go)" test -v ./e2e/...
"$(which go)" test -v ./e2e/... ${TESTFLAGS-}
}
export unique_id="${E2E_UNIQUE_ID:-cliendtoendsuite}"

View File

@ -27,6 +27,7 @@ testexit=0
docker run -i --rm \
-v "$PWD:/go/src/github.com/docker/cli" \
--network "${unique_id}_default" \
-e TESTFLAGS \
"$dev_image" \
./scripts/test/e2e/run test "$engine_host" || testexit="$?"
run_in_env cleanup

View File

@ -11,7 +11,7 @@ for pkg in "$@"; do
-coverprofile=profile.out \
-covermode=atomic \
"${pkg}"
if test -f profile.out; then
cat profile.out >> coverage.txt
rm profile.out

View File

@ -1,9 +1,6 @@
package client
// parse_logs.go contains utility helpers for getting information out of docker
// log lines. really, it only contains ParseDetails right now. maybe in the
// future there will be some desire to parse log messages back into a struct?
// that would go here if we did
/*Package logs contains tools for parsing docker log lines.
*/
package logs
import (
"net/url"
@ -12,12 +9,13 @@ import (
"github.com/pkg/errors"
)
// ParseLogDetails takes a details string of key value pairs in the form
// ParseLogDetails parses a string of key value pairs in the form
// "k=v,l=w", where the keys and values are url query escaped, and each pair
// is separated by a comma, returns a map. returns an error if the details
// string is not in a valid format
// the exact form of details encoding is implemented in
// api/server/httputils/write_log_stream.go
// is separated by a comma. Returns a map of the key value pairs on success,
// and an error if the details string is not in a valid format.
//
// The details string encoding is implemented in
// github.com/moby/moby/api/server/httputils/write_log_stream.go
func ParseLogDetails(details string) (map[string]string, error) {
pairs := strings.Split(details, ",")
detailsMap := make(map[string]string, len(pairs))

View File

@ -0,0 +1,33 @@
package logs
import (
"testing"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestParseLogDetails(t *testing.T) {
testCases := []struct {
line string
expected map[string]string
err error
}{
{"key=value", map[string]string{"key": "value"}, nil},
{"key1=value1,key2=value2", map[string]string{"key1": "value1", "key2": "value2"}, nil},
{"key+with+spaces=value%3Dequals,asdf%2C=", map[string]string{"key with spaces": "value=equals", "asdf,": ""}, nil},
{"key=,=nothing", map[string]string{"key": "", "": "nothing"}, nil},
{"=", map[string]string{"": ""}, nil},
{"errors", nil, errors.New("invalid details format")},
}
for _, testcase := range testCases {
t.Run(testcase.line, func(t *testing.T) {
actual, err := ParseLogDetails(testcase.line)
if testcase.err != nil {
assert.EqualError(t, err, testcase.err.Error())
return
}
assert.Equal(t, testcase.expected, actual)
})
}
}

View File

@ -1,56 +1,56 @@
github.com/Azure/go-ansiterm 388960b655244e76e24c75f48631564eaefade62
github.com/Microsoft/go-winio v0.4.2
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty
github.com/Sirupsen/logrus v0.11.0
github.com/agl/ed25519 d2b94fd789ea21d12fac1a4443dd3a3f79cda72c
github.com/Azure/go-ansiterm 19f72df4d05d31cbe1c56bfc8045c96babff6c7e
github.com/coreos/etcd 824277cb3a577a0e8c829ca9ec557b973fe06d20
github.com/cpuguy83/go-md2man a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa
github.com/davecgh/go-spew 346938d642f2ec3594ed81d874461961cd0faa76
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621
github.com/docker/docker d58ffa0364c04d03a8f25704d7f0489ee6cd9634
github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c
github.com/docker/docker 84144a8c66c1bb2af8fa997288f51ef2719971b4
github.com/docker/docker-credential-helpers v0.5.1
# the docker/go package contains a customized version of canonical/json
# and is used by Notary. The package is periodically rebased on current Go versions.
github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06
github.com/docker/go-connections 3ede32e2033de7505e6500d6c868c2b9ed9f169d
github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
github.com/docker/go-units 9e638d38cf6977a37a8ea0078f3ee75a7cdb2dd1
github.com/docker/libtrust 9cbd2a1374f46905c68a4eb3694a130610adc62a
github.com/docker/notary v0.4.2
github.com/docker/swarmkit 79381d0840be27f8b3f5c667b348a4467d866eeb
github.com/docker/notary v0.4.2-sirupsen https://github.com/simonferquel/notary.git
github.com/docker/swarmkit 0554c9bc9a485025e89b8e5c2c1f0d75961906a2
github.com/flynn-archive/go-shlex 3f9db97f856818214da2e1057f8ad84803971cff
github.com/gogo/protobuf 7efa791bd276fd4db00867cbd982b552627c24cb
github.com/gogo/protobuf v0.4
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/gotestyourself/gotestyourself v1.2.0
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
github.com/mattn/go-shellwords v1.0.3
github.com/Microsoft/go-winio v0.4.4
github.com/miekg/pkcs11 df8ae6ca730422dba20c768ff38ef7d79077a59f
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/moby/buildkit da2b9dc7dab99e824b2b1067ad7d0523e32dd2d9 https://github.com/dmcgowan/buildkit.git
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty
github.com/opencontainers/go-digest 21dfd564fd89c944783d00d069f33e3e7123c448
github.com/opencontainers/image-spec v1.0.0
github.com/opencontainers/runc d40db12e72a40109dfcf28539f5ee0930d2f0277
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
github.com/pmezard/go-difflib v1.0.0
github.com/russross/blackfriday 1d6b8e9301e720b08a8938b8c25c018285885438
github.com/shurcooL/sanitized_anchor_name 10ef21a441db47d8b13ebcc5fd2310f636973c77
github.com/sirupsen/logrus v1.0.1
github.com/spf13/cobra v1.5.1 https://github.com/dnephin/cobra.git
github.com/spf13/pflag 9ff6c6923cfffbcd502984b8e0c80539a94968b7
github.com/stevvooe/continuity cd7a8e21e2b6f84799f5dd4b65faf49c8d3ee02d
github.com/stretchr/testify 4d4bfba8f1d1027c4fdbe371823030df51419987
github.com/tonistiigi/fsutil 0ac4c11b053b9c5c7c47558f81f96c7100ce50fb
github.com/xeipuuv/gojsonpointer e0fe6f68307607d540ed8eac07a342c33fa1b54a
github.com/xeipuuv/gojsonreference e02fc20de94c78484cd5ffb007f8af96be030a45
github.com/xeipuuv/gojsonschema 93e72a773fade158921402d6a24c819b48aba29d
golang.org/x/crypto 3fbbcd23f1cb824e69491a5930cfeff09b12f4d2
golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6
golang.org/x/sys 8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
golang.org/x/sys 07c182904dbd53199946ba614a412c61d3c548f5
golang.org/x/text f72d8390a633d5dfb0cc84043294db9f6c935756
golang.org/x/time a4bde12657593d5e90d0533a3e4fd95e635124cb
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
google.golang.org/grpc v1.3.0
gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6
github.com/tonistiigi/fsutil 0ac4c11b053b9c5c7c47558f81f96c7100ce50fb
github.com/stevvooe/continuity cd7a8e21e2b6f84799f5dd4b65faf49c8d3ee02d
golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0
vbom.ml/util 928aaa586d7718c70f4090ddf83f2b34c16fdc8d

View File

@ -7,3 +7,6 @@ For example the parser might receive "ESC, [, A" as a stream of three characters
The parser (parser.go) is a partial implementation of this state machine (http://vt100.net/emu/vt500_parser.png). There are also two event handler implementations, one for tests (test_event_handler.go) to validate that the expected events are being produced and called, the other is a Windows implementation (winterm/win_event_handler.go).
See parser_test.go for examples exercising the state machine and generating appropriate function calls.
-----
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.

View File

@ -5,7 +5,7 @@ import (
"io/ioutil"
"os"
"github.com/Sirupsen/logrus"
"github.com/sirupsen/logrus"
)
var logger *logrus.Logger

View File

@ -9,7 +9,7 @@ import (
"strconv"
"github.com/Azure/go-ansiterm"
"github.com/Sirupsen/logrus"
"github.com/sirupsen/logrus"
)
var logger *logrus.Logger

View File

@ -23,6 +23,13 @@ type atomicBool int32
func (b *atomicBool) isSet() bool { return atomic.LoadInt32((*int32)(b)) != 0 }
func (b *atomicBool) setFalse() { atomic.StoreInt32((*int32)(b), 0) }
func (b *atomicBool) setTrue() { atomic.StoreInt32((*int32)(b), 1) }
func (b *atomicBool) swap(new bool) bool {
var newInt int32
if new {
newInt = 1
}
return atomic.SwapInt32((*int32)(b), newInt) == 1
}
const (
cFILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 1
@ -71,7 +78,7 @@ func initIo() {
type win32File struct {
handle syscall.Handle
wg sync.WaitGroup
closing bool
closing atomicBool
readDeadline deadlineHandler
writeDeadline deadlineHandler
}
@ -107,9 +114,9 @@ func MakeOpenFile(h syscall.Handle) (io.ReadWriteCloser, error) {
// closeHandle closes the resources associated with a Win32 handle
func (f *win32File) closeHandle() {
if !f.closing {
// Atomically set that we are closing, releasing the resources only once.
if !f.closing.swap(true) {
// cancel all IO and wait for it to complete
f.closing = true
cancelIoEx(f.handle, nil)
f.wg.Wait()
// at this point, no new IO can start
@ -127,10 +134,10 @@ func (f *win32File) Close() error {
// prepareIo prepares for a new IO operation.
// The caller must call f.wg.Done() when the IO is finished, prior to Close() returning.
func (f *win32File) prepareIo() (*ioOperation, error) {
f.wg.Add(1)
if f.closing {
if f.closing.isSet() {
return nil, ErrFileClosed
}
f.wg.Add(1)
c := &ioOperation{}
c.ch = make(chan ioResult)
return c, nil
@ -159,7 +166,7 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er
return int(bytes), err
}
if f.closing {
if f.closing.isSet() {
cancelIoEx(f.handle, &c.o)
}
@ -175,7 +182,7 @@ func (f *win32File) asyncIo(c *ioOperation, d *deadlineHandler, bytes uint32, er
case r = <-c.ch:
err = r.err
if err == syscall.ERROR_OPERATION_ABORTED {
if f.closing {
if f.closing.isSet() {
err = ErrFileClosed
}
}

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