Merge component 'cli' from git@github.com:docker/cli master
This commit is contained in:
@ -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"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
[](https://circleci.com/gh/docker/cli/tree/master)
|
||||
[](https://circleci.com/gh/docker/cli/tree/master) [](https://jenkins.dockerproject.org/job/docker/job/cli/job/master/)
|
||||
|
||||
docker/cli
|
||||
==========
|
||||
|
||||
@ -1 +1 @@
|
||||
17.08.0-dev
|
||||
17.10.0-dev
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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")),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -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")),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -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}}")},
|
||||
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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)},
|
||||
|
||||
@ -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)},
|
||||
|
||||
3
components/cli/cli/command/formatter/testdata/container-context-write-special-headers.golden
vendored
Normal file
3
components/cli/cli/command/formatter/testdata/container-context-write-special-headers.golden
vendored
Normal 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
|
||||
5
components/cli/cli/command/formatter/testdata/disk-usage-context-write-custom.golden
vendored
Normal file
5
components/cli/cli/command/formatter/testdata/disk-usage-context-write-custom.golden
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
TYPE ACTIVE
|
||||
Images 0
|
||||
Containers 0
|
||||
Local Volumes 0
|
||||
Build Cache
|
||||
24
components/cli/cli/command/formatter/testdata/disk-usage-raw-format.golden
vendored
Normal file
24
components/cli/cli/command/formatter/testdata/disk-usage-raw-format.golden
vendored
Normal 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
|
||||
|
||||
2
components/cli/cli/command/formatter/testdata/search-context-write-stars-table.golden
vendored
Normal file
2
components/cli/cli/command/formatter/testdata/search-context-write-stars-table.golden
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
|
||||
result1 Official build 5000 [OK]
|
||||
3
components/cli/cli/command/formatter/testdata/search-context-write-table.golden
vendored
Normal file
3
components/cli/cli/command/formatter/testdata/search-context-write-table.golden
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
|
||||
result1 Official build 5000 [OK]
|
||||
result2 Not official 5 [OK]
|
||||
14
components/cli/cli/command/formatter/testdata/service-context-write-raw.golden
vendored
Normal file
14
components/cli/cli/command/formatter/testdata/service-context-write-raw.golden
vendored
Normal 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
|
||||
|
||||
3
components/cli/cli/command/formatter/testdata/task-context-write-table-custom.golden
vendored
Normal file
3
components/cli/cli/command/formatter/testdata/task-context-write-table-custom.golden
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
NAME NODE PORTS
|
||||
foobar_baz foo1
|
||||
foobar_bar foo2
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
69
components/cli/cli/command/network/list_test.go
Normal file
69
components/cli/cli/command/network/list_test.go
Normal 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")
|
||||
}
|
||||
2
components/cli/cli/command/network/testdata/network-list.golden
vendored
Normal file
2
components/cli/cli/command/network/testdata/network-list.golden
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
NETWORK ID NAME DRIVER SCOPE
|
||||
123454321 network_1 09.7.01 global
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -154,6 +154,7 @@ func TestSecretInspectPretty(t *testing.T) {
|
||||
}),
|
||||
SecretID("secretID"),
|
||||
SecretName("secretName"),
|
||||
SecretDriver("driver"),
|
||||
SecretCreatedAt(time.Time{}),
|
||||
SecretUpdatedAt(time.Time{}),
|
||||
), []byte{}, nil
|
||||
|
||||
@ -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
|
||||
},
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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{
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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"})
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
},
|
||||
},
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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])
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ package debug
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Enable sets the DEBUG env var to true
|
||||
|
||||
@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func TestEnable(t *testing.T) {
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
@ -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"
|
||||
)
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
Binary file not shown.
@ -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
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
17
components/cli/e2e/container/main_test.go
Normal file
17
components/cli/e2e/container/main_test.go
Normal 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())
|
||||
}
|
||||
42
components/cli/e2e/container/run_test.go
Normal file
42
components/cli/e2e/container/run_test.go
Normal 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}
|
||||
}
|
||||
4
components/cli/e2e/container/testdata/run-attached-from-remote-and-remove.golden
vendored
Normal file
4
components/cli/e2e/container/testdata/run-attached-from-remote-and-remove.golden
vendored
Normal 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
|
||||
@ -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)
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
45
components/cli/internal/test/builders/network.go
Normal file
45
components/cli/internal/test/builders/network.go
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
@ -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}}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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",
|
||||
},
|
||||
}
|
||||
|
||||
21
components/cli/internal/test/environment/testenv.go
Normal file
21
components/cli/internal/test/environment/testenv.go
Normal 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)
|
||||
@ -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
|
||||
|
||||
@ -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}"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
33
components/cli/service/logs/parse_logs_test.go
Normal file
33
components/cli/service/logs/parse_logs_test.go
Normal 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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
3
components/cli/vendor/github.com/Azure/go-ansiterm/README.md
generated
vendored
3
components/cli/vendor/github.com/Azure/go-ansiterm/README.md
generated
vendored
@ -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.
|
||||
|
||||
2
components/cli/vendor/github.com/Azure/go-ansiterm/parser.go
generated
vendored
2
components/cli/vendor/github.com/Azure/go-ansiterm/parser.go
generated
vendored
@ -5,7 +5,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var logger *logrus.Logger
|
||||
|
||||
2
components/cli/vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go
generated
vendored
2
components/cli/vendor/github.com/Azure/go-ansiterm/winterm/win_event_handler.go
generated
vendored
@ -9,7 +9,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
"github.com/Azure/go-ansiterm"
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var logger *logrus.Logger
|
||||
|
||||
21
components/cli/vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
21
components/cli/vendor/github.com/Microsoft/go-winio/file.go
generated
vendored
@ -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
Reference in New Issue
Block a user