mirror of
https://github.com/docker/cli.git
synced 2026-11-12 15:53:52 -04:00
Compare commits
252
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2ae869f6f | ||
|
|
402c2bcea9 | ||
|
|
76782187a0 | ||
|
|
50c4621fc8 | ||
|
|
6219337a37 | ||
|
|
d3c4d8afd9 | ||
|
|
986febc10e | ||
|
|
400686e93f | ||
|
|
0fcc210e23 | ||
|
|
f2e7dc5bf8 | ||
|
|
575d6fa782 | ||
|
|
1b6c0f359f | ||
|
|
fbb91c78fd | ||
|
|
a81ecb1e1b | ||
|
|
4ef9677df6 | ||
|
|
5344ecdeb9 | ||
|
|
705cc2040d | ||
|
|
6dd55805ac | ||
|
|
9f9bf03a18 | ||
|
|
9e9fa7a751 | ||
|
|
87d91a1569 | ||
|
|
f373eee98d | ||
|
|
880e763594 | ||
|
|
8a0a5f0de6 | ||
|
|
7a964b3b60 | ||
|
|
9787a2d94b | ||
|
|
47a40bd409 | ||
|
|
7d9172058d | ||
|
|
1752eb3626 | ||
|
|
feb68f9055 | ||
|
|
aed09dc7eb | ||
|
|
f3af74c18c | ||
|
|
ea2b474196 | ||
|
|
24dcc56123 | ||
|
|
31c078b66d | ||
|
|
0feb4080ba | ||
|
|
9c8ac0a123 | ||
|
|
a88330d9dd | ||
|
|
62f123fbd2 | ||
|
|
7b3d023163 | ||
|
|
32c4d8c40f | ||
|
|
773a91f9e9 | ||
|
|
1764ffafca | ||
|
|
c1c3add698 | ||
|
|
e298714728 | ||
|
|
1de93b845b | ||
|
|
3d0a1f66eb | ||
|
|
e065aa2798 | ||
|
|
5f4a501c27 | ||
|
|
7969d87630 | ||
|
|
642235d082 | ||
|
|
0dff33436c | ||
|
|
048af5b37b | ||
|
|
7ee8241f71 | ||
|
|
74d51dc13b | ||
|
|
a757fad956 | ||
|
|
ec2ecc1c8b | ||
|
|
23c88a8311 | ||
|
|
6be8fce6f8 | ||
|
|
667aef15b0 | ||
|
|
3a749342a3 | ||
|
|
9bd840b234 | ||
|
|
b8fe5cea3d | ||
|
|
1b3ed84535 | ||
|
|
85a73e440e | ||
|
|
9959062d9a | ||
|
|
dcc8f14cad | ||
|
|
a378a00954 | ||
|
|
534c774fab | ||
|
|
c1c4b46f29 | ||
|
|
db7875928c | ||
|
|
206ea57da8 | ||
|
|
edbc0e0613 | ||
|
|
dbd66addb9 | ||
|
|
f9a42a4024 | ||
|
|
a13ec91543 | ||
|
|
278d30bceb | ||
|
|
65b28186fc | ||
|
|
c89750f836 | ||
|
|
c805ad2964 | ||
|
|
d8c6c830f8 | ||
|
|
f89d05edcb | ||
|
|
90fa621791 | ||
|
|
e1fe8f3c45 | ||
|
|
356eda4028 | ||
|
|
85148aa3f1 | ||
|
|
19c0311d46 | ||
|
|
207ff0831d | ||
|
|
57b27434ea | ||
|
|
010c234a0d | ||
|
|
9a5296c8f1 | ||
|
|
b59752479b | ||
|
|
8997667aa2 | ||
|
|
bcae2c4408 | ||
|
|
079adf3f23 | ||
|
|
f6693b0b25 | ||
|
|
ed16a3136b | ||
|
|
e63ac0ea35 | ||
|
|
c1a4358ea4 | ||
|
|
27ab7cc3d6 | ||
|
|
74bd5f143f | ||
|
|
8dc400713f | ||
|
|
543f9b32ee | ||
|
|
1d314f2227 | ||
|
|
275ab1f063 | ||
|
|
4f6ab11ff4 | ||
|
|
537309a548 | ||
|
|
08714b4579 | ||
|
|
789a15bc73 | ||
|
|
ce12ac2d14 | ||
|
|
4c94a0af75 | ||
|
|
0717f261ed | ||
|
|
fc8717799f | ||
|
|
76f4876129 | ||
|
|
7ea48a16e3 | ||
|
|
75e9075591 | ||
|
|
69e1094f5a | ||
|
|
890e29da87 | ||
|
|
78d52ec5d4 | ||
|
|
c0bbca75af | ||
|
|
b666e9a090 | ||
|
|
9352be5341 | ||
|
|
b4f607fb4f | ||
|
|
af2647d55b | ||
|
|
c71aa11c0a | ||
|
|
336b2a5cac | ||
|
|
c462e06fcd | ||
|
|
719508a935 | ||
|
|
2fa3aae9ed | ||
|
|
6c3a10aaed | ||
|
|
3ee6755815 | ||
|
|
16349f6e33 | ||
|
|
2aa77af30f | ||
|
|
456c1ce695 | ||
|
|
bcadc9061c | ||
|
|
e05745b4a5 | ||
|
|
b6ecef353f | ||
|
|
e380ddaddf | ||
|
|
12834eeff6 | ||
|
|
bb46da9fba | ||
|
|
871d24d3fc | ||
|
|
61a9096b8d | ||
|
|
2ac475cf97 | ||
|
|
2a36695037 | ||
|
|
dc74fc81f2 | ||
|
|
7e90635652 | ||
|
|
3f7989903a | ||
|
|
7059d069c3 | ||
|
|
4a4a1f3615 | ||
|
|
1274f23252 | ||
|
|
3af1848dda | ||
|
|
6d91f5d55d | ||
|
|
d56948c12c | ||
|
|
9b3eea87ee | ||
|
|
31c092e155 | ||
|
|
046ffa4e87 | ||
|
|
51668a30f2 | ||
|
|
5e7f9d3c84 | ||
|
|
72ddefbada | ||
|
|
135aa72476 | ||
|
|
7c7fe26a6f | ||
|
|
1df47ffb4d | ||
|
|
2e7e529a18 | ||
|
|
f8f230181e | ||
|
|
0ee4693953 | ||
|
|
cb4cd04c64 | ||
|
|
d2e771fed6 | ||
|
|
b8911a3b33 | ||
|
|
ebe071a9b3 | ||
|
|
ecb972ab38 | ||
|
|
4c68a9666f | ||
|
|
e245b72381 | ||
|
|
0ff9e5cd10 | ||
|
|
8e565d0399 | ||
|
|
8a424333f9 | ||
|
|
fde819236b | ||
|
|
aa6314c663 | ||
|
|
81ee98e861 | ||
|
|
8ae4453d46 | ||
|
|
aeea559129 | ||
|
|
22336b332c | ||
|
|
2961611fda | ||
|
|
17adf05188 | ||
|
|
39f1110308 | ||
|
|
3dfacb55a4 | ||
|
|
e942084530 | ||
|
|
50f529fa47 | ||
|
|
b4bee9be75 | ||
|
|
8b0d34a5a1 | ||
|
|
f93908213a | ||
|
|
4280972d65 | ||
|
|
984bc7411e | ||
|
|
92932647d3 | ||
|
|
dee37936e5 | ||
|
|
3e1a0bdc23 | ||
|
|
f2b2061cc3 | ||
|
|
4925fd9c34 | ||
|
|
5d3ab5bc0c | ||
|
|
c12e23a4c1 | ||
|
|
aca3f2d382 | ||
|
|
a7488d1bcd | ||
|
|
5a97a93ae1 | ||
|
|
41910b6d68 | ||
|
|
1a087e87c9 | ||
|
|
0b11120060 | ||
|
|
e57b20642d | ||
|
|
b8702b8a9a | ||
|
|
a31b20d7db | ||
|
|
5ba5678898 | ||
|
|
9de1318e36 | ||
|
|
19e1ab273e | ||
|
|
ec1812188f | ||
|
|
6004d74b1f | ||
|
|
e79e591ee9 | ||
|
|
0f22d7e295 | ||
|
|
f250152bf4 | ||
|
|
f9d666b057 | ||
|
|
342afe44fb | ||
|
|
cfec8027ed | ||
|
|
78c42cf031 | ||
|
|
dd2f13bed4 | ||
|
|
3b991ec615 | ||
|
|
34ea8bb5a5 | ||
|
|
afb17ec70b | ||
|
|
62aed95bc1 | ||
|
|
649e4916bb | ||
|
|
3597d75281 | ||
|
|
5673816fec | ||
|
|
a8c69c8287 | ||
|
|
fc3dc8f058 | ||
|
|
2a46a3d46c | ||
|
|
b2cf18ac2e | ||
|
|
44371c7c34 | ||
|
|
4e6798794d | ||
|
|
d8aefad94a | ||
|
|
3c37d6a034 | ||
|
|
9d43f1ed48 | ||
|
|
a818677813 | ||
|
|
c204959687 | ||
|
|
76c09259db | ||
|
|
0efb62cab1 | ||
|
|
8789e93d6e | ||
|
|
0ee05a6353 | ||
|
|
68be7cb376 | ||
|
|
de805da04c | ||
|
|
b75350de7a | ||
|
|
f96ddaedf7 | ||
|
|
0fb6bb35a4 | ||
|
|
264ee43c2a | ||
|
|
7f4c842e8a | ||
|
|
e25e9d68be | ||
|
|
6877dedeee |
@@ -12,14 +12,18 @@ clean: ## remove build artifacts
|
|||||||
|
|
||||||
.PHONY: test-unit
|
.PHONY: test-unit
|
||||||
test-unit: ## run unit test
|
test-unit: ## run unit test
|
||||||
./scripts/test/unit $(shell go list ./... | grep -vE '/vendor/|/e2e/|/e2eengine/')
|
./scripts/test/unit $(shell go list ./... | grep -vE '/vendor/|/e2e/')
|
||||||
|
|
||||||
.PHONY: test
|
.PHONY: test
|
||||||
test: test-unit ## run tests
|
test: test-unit ## run tests
|
||||||
|
|
||||||
.PHONY: test-coverage
|
.PHONY: test-coverage
|
||||||
test-coverage: ## run test coverage
|
test-coverage: ## run test coverage
|
||||||
./scripts/test/unit-with-coverage $(shell go list ./... | grep -vE '/vendor/|/e2e/|/e2eengine/')
|
./scripts/test/unit-with-coverage $(shell go list ./... | grep -vE '/vendor/|/e2e/')
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt:
|
||||||
|
go list -f {{.Dir}} ./... | xargs gofmt -w -s -d
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: ## run all the lint tools
|
lint: ## run all the lint tools
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ clone_folder: c:\gopath\src\github.com\docker\cli
|
|||||||
|
|
||||||
environment:
|
environment:
|
||||||
GOPATH: c:\gopath
|
GOPATH: c:\gopath
|
||||||
GOVERSION: 1.10.3
|
GOVERSION: 1.12.12
|
||||||
DEPVERSION: v0.4.1
|
DEPVERSION: v0.4.1
|
||||||
|
|
||||||
install:
|
install:
|
||||||
@@ -20,4 +20,4 @@ build_script:
|
|||||||
- ps: .\scripts\make.ps1 -Binary
|
- ps: .\scripts\make.ps1 -Binary
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- ps: .\scripts\make.ps1 -TestUnit
|
- ps: .\scripts\make.ps1 -TestUnit
|
||||||
|
|||||||
+6
-15
@@ -16,9 +16,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: "Lint"
|
name: "Lint"
|
||||||
command: |
|
command: |
|
||||||
dockerfile=dockerfiles/Dockerfile.lint
|
docker build -f dockerfiles/Dockerfile.lint --tag cli-linter:$CIRCLE_BUILD_NUM .
|
||||||
echo "COPY . ." >> $dockerfile
|
|
||||||
docker build -f $dockerfile --tag cli-linter:$CIRCLE_BUILD_NUM .
|
|
||||||
docker run --rm cli-linter:$CIRCLE_BUILD_NUM
|
docker run --rm cli-linter:$CIRCLE_BUILD_NUM
|
||||||
|
|
||||||
cross:
|
cross:
|
||||||
@@ -34,9 +32,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: "Cross"
|
name: "Cross"
|
||||||
command: |
|
command: |
|
||||||
dockerfile=dockerfiles/Dockerfile.cross
|
docker build -f dockerfiles/Dockerfile.cross --tag cli-builder:$CIRCLE_BUILD_NUM .
|
||||||
echo "COPY . ." >> $dockerfile
|
|
||||||
docker build -f $dockerfile --tag cli-builder:$CIRCLE_BUILD_NUM .
|
|
||||||
name=cross-$CIRCLE_BUILD_NUM-$CIRCLE_NODE_INDEX
|
name=cross-$CIRCLE_BUILD_NUM-$CIRCLE_NODE_INDEX
|
||||||
docker run \
|
docker run \
|
||||||
-e CROSS_GROUP=$CIRCLE_NODE_INDEX \
|
-e CROSS_GROUP=$CIRCLE_NODE_INDEX \
|
||||||
@@ -60,9 +56,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: "Unit Test with Coverage"
|
name: "Unit Test with Coverage"
|
||||||
command: |
|
command: |
|
||||||
dockerfile=dockerfiles/Dockerfile.dev
|
docker build -f dockerfiles/Dockerfile.dev --tag cli-builder:$CIRCLE_BUILD_NUM .
|
||||||
echo "COPY . ." >> $dockerfile
|
|
||||||
docker build -f $dockerfile --tag cli-builder:$CIRCLE_BUILD_NUM .
|
|
||||||
docker run --name \
|
docker run --name \
|
||||||
test-$CIRCLE_BUILD_NUM cli-builder:$CIRCLE_BUILD_NUM \
|
test-$CIRCLE_BUILD_NUM cli-builder:$CIRCLE_BUILD_NUM \
|
||||||
make test-coverage
|
make test-coverage
|
||||||
@@ -89,12 +83,11 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: "Validate Vendor, Docs, and Code Generation"
|
name: "Validate Vendor, Docs, and Code Generation"
|
||||||
command: |
|
command: |
|
||||||
dockerfile=dockerfiles/Dockerfile.dev
|
|
||||||
echo "COPY . ." >> $dockerfile
|
|
||||||
rm -f .dockerignore # include .git
|
rm -f .dockerignore # include .git
|
||||||
docker build -f $dockerfile --tag cli-builder-with-git:$CIRCLE_BUILD_NUM .
|
docker build -f dockerfiles/Dockerfile.dev --tag cli-builder-with-git:$CIRCLE_BUILD_NUM .
|
||||||
docker run --rm cli-builder-with-git:$CIRCLE_BUILD_NUM \
|
docker run --rm cli-builder-with-git:$CIRCLE_BUILD_NUM \
|
||||||
make ci-validate
|
make ci-validate
|
||||||
|
no_output_timeout: 15m
|
||||||
shellcheck:
|
shellcheck:
|
||||||
working_directory: /work
|
working_directory: /work
|
||||||
docker: [{image: 'docker:18.03-git'}]
|
docker: [{image: 'docker:18.03-git'}]
|
||||||
@@ -107,9 +100,7 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: "Run shellcheck"
|
name: "Run shellcheck"
|
||||||
command: |
|
command: |
|
||||||
dockerfile=dockerfiles/Dockerfile.shellcheck
|
docker build -f dockerfiles/Dockerfile.shellcheck --tag cli-validator:$CIRCLE_BUILD_NUM .
|
||||||
echo "COPY . ." >> $dockerfile
|
|
||||||
docker build -f $dockerfile --tag cli-validator:$CIRCLE_BUILD_NUM .
|
|
||||||
docker run --rm cli-validator:$CIRCLE_BUILD_NUM \
|
docker run --rm cli-validator:$CIRCLE_BUILD_NUM \
|
||||||
make shellcheck
|
make shellcheck
|
||||||
workflows:
|
workflows:
|
||||||
|
|||||||
@@ -3,29 +3,94 @@ package builder
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type pruneOptions struct {
|
||||||
|
force bool
|
||||||
|
all bool
|
||||||
|
filter opts.FilterOpt
|
||||||
|
keepStorage opts.MemBytes
|
||||||
|
}
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for images
|
// NewPruneCommand returns a new cobra prune command for images
|
||||||
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
options := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune",
|
Use: "prune",
|
||||||
Short: "Remove build cache",
|
Short: "Remove build cache",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
report, err := dockerCli.Client().BuildCachePrune(context.Background())
|
spaceReclaimed, output, err := runPrune(dockerCli, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(report.SpaceReclaimed)))
|
if output != "" {
|
||||||
|
fmt.Fprintln(dockerCli.Out(), output)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{"version": "1.39"},
|
Annotations: map[string]string{"version": "1.39"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
|
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones")
|
||||||
|
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'unused-for=24h')")
|
||||||
|
flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
normalWarning = `WARNING! This will remove all dangling build cache. Are you sure you want to continue?`
|
||||||
|
allCacheWarning = `WARNING! This will remove all build cache. Are you sure you want to continue?`
|
||||||
|
)
|
||||||
|
|
||||||
|
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||||
|
pruneFilters := options.filter.Value()
|
||||||
|
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
||||||
|
|
||||||
|
warning := normalWarning
|
||||||
|
if options.all {
|
||||||
|
warning = allCacheWarning
|
||||||
|
}
|
||||||
|
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||||
|
return 0, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
report, err := dockerCli.Client().BuildCachePrune(context.Background(), types.BuildCachePruneOptions{
|
||||||
|
All: options.all,
|
||||||
|
KeepStorage: options.keepStorage.Value(),
|
||||||
|
Filters: pruneFilters,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(report.CachesDeleted) > 0 {
|
||||||
|
var sb strings.Builder
|
||||||
|
sb.WriteString("Deleted build cache objects:\n")
|
||||||
|
for _, id := range report.CachesDeleted {
|
||||||
|
sb.WriteString(id)
|
||||||
|
sb.WriteByte('\n')
|
||||||
|
}
|
||||||
|
output = sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return report.SpaceReclaimed, output, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CachePrune executes a prune command for build cache
|
||||||
|
func CachePrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
|
return runPrune(dockerCli, pruneOptions{force: true, all: all, filter: filter})
|
||||||
|
}
|
||||||
|
|||||||
+35
-22
@@ -8,6 +8,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
@@ -19,8 +20,8 @@ import (
|
|||||||
manifeststore "github.com/docker/cli/cli/manifest/store"
|
manifeststore "github.com/docker/cli/cli/manifest/store"
|
||||||
registryclient "github.com/docker/cli/cli/registry/client"
|
registryclient "github.com/docker/cli/cli/registry/client"
|
||||||
"github.com/docker/cli/cli/trust"
|
"github.com/docker/cli/cli/trust"
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
|
||||||
dopts "github.com/docker/cli/opts"
|
dopts "github.com/docker/cli/opts"
|
||||||
|
clitypes "github.com/docker/cli/types"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
@@ -55,20 +56,21 @@ type Cli interface {
|
|||||||
ManifestStore() manifeststore.Store
|
ManifestStore() manifeststore.Store
|
||||||
RegistryClient(bool) registryclient.RegistryClient
|
RegistryClient(bool) registryclient.RegistryClient
|
||||||
ContentTrustEnabled() bool
|
ContentTrustEnabled() bool
|
||||||
NewContainerizedEngineClient(sockPath string) (containerizedengine.Client, error)
|
NewContainerizedEngineClient(sockPath string) (clitypes.ContainerizedClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DockerCli is an instance the docker command line client.
|
// DockerCli is an instance the docker command line client.
|
||||||
// Instances of the client can be returned from NewDockerCli.
|
// Instances of the client can be returned from NewDockerCli.
|
||||||
type DockerCli struct {
|
type DockerCli struct {
|
||||||
configFile *configfile.ConfigFile
|
configFile *configfile.ConfigFile
|
||||||
in *InStream
|
in *InStream
|
||||||
out *OutStream
|
out *OutStream
|
||||||
err io.Writer
|
err io.Writer
|
||||||
client client.APIClient
|
client client.APIClient
|
||||||
serverInfo ServerInfo
|
serverInfo ServerInfo
|
||||||
clientInfo ClientInfo
|
clientInfo ClientInfo
|
||||||
contentTrust bool
|
contentTrust bool
|
||||||
|
newContainerizeClient func(string) (clitypes.ContainerizedClient, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
|
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
|
||||||
@@ -132,6 +134,20 @@ func (cli *DockerCli) ContentTrustEnabled() bool {
|
|||||||
return cli.contentTrust
|
return cli.contentTrust
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BuildKitEnabled returns whether buildkit is enabled either through a daemon setting
|
||||||
|
// or otherwise the client-side DOCKER_BUILDKIT environment variable
|
||||||
|
func BuildKitEnabled(si ServerInfo) (bool, error) {
|
||||||
|
buildkitEnabled := si.BuildkitVersion == types.BuilderBuildKit
|
||||||
|
if buildkitEnv := os.Getenv("DOCKER_BUILDKIT"); buildkitEnv != "" {
|
||||||
|
var err error
|
||||||
|
buildkitEnabled, err = strconv.ParseBool(buildkitEnv)
|
||||||
|
if err != nil {
|
||||||
|
return false, errors.Wrap(err, "DOCKER_BUILDKIT environment variable expects boolean value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buildkitEnabled, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ManifestStore returns a store for local manifests
|
// ManifestStore returns a store for local manifests
|
||||||
func (cli *DockerCli) ManifestStore() manifeststore.Store {
|
func (cli *DockerCli) ManifestStore() manifeststore.Store {
|
||||||
// TODO: support override default location from config file
|
// TODO: support override default location from config file
|
||||||
@@ -233,8 +249,8 @@ func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewContainerizedEngineClient returns a containerized engine client
|
// NewContainerizedEngineClient returns a containerized engine client
|
||||||
func (cli *DockerCli) NewContainerizedEngineClient(sockPath string) (containerizedengine.Client, error) {
|
func (cli *DockerCli) NewContainerizedEngineClient(sockPath string) (clitypes.ContainerizedClient, error) {
|
||||||
return containerizedengine.NewClient(sockPath)
|
return cli.newContainerizeClient(sockPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerInfo stores details about the supported features and platform of the
|
// ServerInfo stores details about the supported features and platform of the
|
||||||
@@ -252,27 +268,23 @@ type ClientInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
||||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer, isTrusted bool) *DockerCli {
|
func NewDockerCli(in io.ReadCloser, out, err io.Writer, isTrusted bool, containerizedFn func(string) (clitypes.ContainerizedClient, error)) *DockerCli {
|
||||||
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err, contentTrust: isTrusted}
|
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err, contentTrust: isTrusted, newContainerizeClient: containerizedFn}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAPIClientFromFlags creates a new APIClient from command line flags
|
// NewAPIClientFromFlags creates a new APIClient from command line flags
|
||||||
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
||||||
unparsedHost, err := getUnparsedServerHost(opts.Hosts)
|
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &client.Client{}, err
|
return &client.Client{}, err
|
||||||
}
|
}
|
||||||
var clientOpts []func(*client.Client) error
|
var clientOpts []func(*client.Client) error
|
||||||
helper, err := connhelper.GetConnectionHelper(unparsedHost)
|
helper, err := connhelper.GetConnectionHelper(host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &client.Client{}, err
|
return &client.Client{}, err
|
||||||
}
|
}
|
||||||
if helper == nil {
|
if helper == nil {
|
||||||
clientOpts = append(clientOpts, withHTTPClient(opts.TLSOptions))
|
clientOpts = append(clientOpts, withHTTPClient(opts.TLSOptions))
|
||||||
host, err := dopts.ParseHost(opts.TLSOptions != nil, unparsedHost)
|
|
||||||
if err != nil {
|
|
||||||
return &client.Client{}, err
|
|
||||||
}
|
|
||||||
clientOpts = append(clientOpts, client.WithHost(host))
|
clientOpts = append(clientOpts, client.WithHost(host))
|
||||||
} else {
|
} else {
|
||||||
clientOpts = append(clientOpts, func(c *client.Client) error {
|
clientOpts = append(clientOpts, func(c *client.Client) error {
|
||||||
@@ -305,7 +317,7 @@ func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.
|
|||||||
return client.NewClientWithOpts(clientOpts...)
|
return client.NewClientWithOpts(clientOpts...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUnparsedServerHost(hosts []string) (string, error) {
|
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) {
|
||||||
var host string
|
var host string
|
||||||
switch len(hosts) {
|
switch len(hosts) {
|
||||||
case 0:
|
case 0:
|
||||||
@@ -315,7 +327,8 @@ func getUnparsedServerHost(hosts []string) (string, error) {
|
|||||||
default:
|
default:
|
||||||
return "", errors.New("Please specify only one -H")
|
return "", errors.New("Please specify only one -H")
|
||||||
}
|
}
|
||||||
return host, nil
|
|
||||||
|
return dopts.ParseHost(tlsOptions != nil, host)
|
||||||
}
|
}
|
||||||
|
|
||||||
func withHTTPClient(tlsOpts *tlsconfig.Options) func(*client.Client) error {
|
func withHTTPClient(tlsOpts *tlsconfig.Options) func(*client.Client) error {
|
||||||
|
|||||||
@@ -43,6 +43,26 @@ func TestNewAPIClientFromFlags(t *testing.T) {
|
|||||||
assert.Check(t, is.Equal(api.DefaultVersion, apiclient.ClientVersion()))
|
assert.Check(t, is.Equal(api.DefaultVersion, apiclient.ClientVersion()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNewAPIClientFromFlagsForDefaultSchema(t *testing.T) {
|
||||||
|
host := ":2375"
|
||||||
|
opts := &flags.CommonOptions{Hosts: []string{host}}
|
||||||
|
configFile := &configfile.ConfigFile{
|
||||||
|
HTTPHeaders: map[string]string{
|
||||||
|
"My-Header": "Custom-Value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
apiclient, err := NewAPIClientFromFlags(opts, configFile)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Check(t, is.Equal("tcp://localhost"+host, apiclient.DaemonHost()))
|
||||||
|
|
||||||
|
expectedHeaders := map[string]string{
|
||||||
|
"My-Header": "Custom-Value",
|
||||||
|
"User-Agent": UserAgent(),
|
||||||
|
}
|
||||||
|
assert.Check(t, is.DeepEqual(expectedHeaders, apiclient.(*client.Client).CustomHTTPHeaders()))
|
||||||
|
assert.Check(t, is.Equal(api.DefaultVersion, apiclient.ClientVersion()))
|
||||||
|
}
|
||||||
|
|
||||||
func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
|
func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
|
||||||
customVersion := "v3.3.3"
|
customVersion := "v3.3.3"
|
||||||
defer env.Patch(t, "DOCKER_API_VERSION", customVersion)()
|
defer env.Patch(t, "DOCKER_API_VERSION", customVersion)()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/builder"
|
"github.com/docker/cli/cli/command/builder"
|
||||||
@@ -85,9 +86,6 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
|||||||
// volume
|
// volume
|
||||||
volume.NewVolumeCommand(dockerCli),
|
volume.NewVolumeCommand(dockerCli),
|
||||||
|
|
||||||
// engine
|
|
||||||
engine.NewEngineCommand(dockerCli),
|
|
||||||
|
|
||||||
// legacy commands may be hidden
|
// legacy commands may be hidden
|
||||||
hide(system.NewEventsCommand(dockerCli)),
|
hide(system.NewEventsCommand(dockerCli)),
|
||||||
hide(system.NewInfoCommand(dockerCli)),
|
hide(system.NewInfoCommand(dockerCli)),
|
||||||
@@ -124,7 +122,10 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
|||||||
hide(image.NewSaveCommand(dockerCli)),
|
hide(image.NewSaveCommand(dockerCli)),
|
||||||
hide(image.NewTagCommand(dockerCli)),
|
hide(image.NewTagCommand(dockerCli)),
|
||||||
)
|
)
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
// engine
|
||||||
|
cmd.AddCommand(engine.NewEngineCommand(dockerCli))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hide(cmd *cobra.Command) *cobra.Command {
|
func hide(cmd *cobra.Command) *cobra.Command {
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.VarP(&createOpts.labels, "label", "l", "Config labels")
|
flags.VarP(&createOpts.labels, "label", "l", "Config labels")
|
||||||
flags.StringVar(&createOpts.templateDriver, "template-driver", "", "Template driver")
|
flags.StringVar(&createOpts.templateDriver, "template-driver", "", "Template driver")
|
||||||
flags.SetAnnotation("driver", "version", []string{"1.37"})
|
flags.SetAnnotation("template-driver", "version", []string{"1.37"})
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,19 +12,24 @@ import (
|
|||||||
|
|
||||||
type fakeClient struct {
|
type fakeClient struct {
|
||||||
client.Client
|
client.Client
|
||||||
inspectFunc func(string) (types.ContainerJSON, error)
|
inspectFunc func(string) (types.ContainerJSON, error)
|
||||||
execInspectFunc func(execID string) (types.ContainerExecInspect, error)
|
execInspectFunc func(execID string) (types.ContainerExecInspect, error)
|
||||||
execCreateFunc func(container string, config types.ExecConfig) (types.IDResponse, error)
|
execCreateFunc func(container string, config types.ExecConfig) (types.IDResponse, error)
|
||||||
createContainerFunc func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error)
|
createContainerFunc func(config *container.Config,
|
||||||
containerStartFunc func(container string, options types.ContainerStartOptions) error
|
hostConfig *container.HostConfig,
|
||||||
imageCreateFunc func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
|
networkingConfig *network.NetworkingConfig,
|
||||||
infoFunc func() (types.Info, error)
|
containerName string) (container.ContainerCreateCreatedBody, error)
|
||||||
containerStatPathFunc func(container, path string) (types.ContainerPathStat, error)
|
containerStartFunc func(container string, options types.ContainerStartOptions) error
|
||||||
containerCopyFromFunc func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
|
imageCreateFunc func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
|
||||||
logFunc func(string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
infoFunc func() (types.Info, error)
|
||||||
waitFunc func(string) (<-chan container.ContainerWaitOKBody, <-chan error)
|
containerStatPathFunc func(container, path string) (types.ContainerPathStat, error)
|
||||||
containerListFunc func(types.ContainerListOptions) ([]types.Container, error)
|
containerCopyFromFunc func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
|
||||||
Version string
|
logFunc func(string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
||||||
|
waitFunc func(string) (<-chan container.ContainerWaitOKBody, <-chan error)
|
||||||
|
containerListFunc func(types.ContainerListOptions) ([]types.Container, error)
|
||||||
|
containerExportFunc func(string) (io.ReadCloser, error)
|
||||||
|
containerExecResizeFunc func(id string, options types.ResizeOptions) error
|
||||||
|
Version string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeClient) ContainerList(_ context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
func (f *fakeClient) ContainerList(_ context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
||||||
@@ -124,3 +129,17 @@ func (f *fakeClient) ContainerStart(_ context.Context, container string, options
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *fakeClient) ContainerExport(_ context.Context, container string) (io.ReadCloser, error) {
|
||||||
|
if f.containerExportFunc != nil {
|
||||||
|
return f.containerExportFunc(container)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakeClient) ContainerExecResize(_ context.Context, id string, options types.ResizeOptions) error {
|
||||||
|
if f.containerExecResizeFunc != nil {
|
||||||
|
return f.containerExecResizeFunc(id, options)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/internal/test"
|
||||||
|
"gotest.tools/assert"
|
||||||
|
"gotest.tools/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContainerExportOutputToFile(t *testing.T) {
|
||||||
|
dir := fs.NewDir(t, "export-test")
|
||||||
|
defer dir.Remove()
|
||||||
|
|
||||||
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
|
containerExportFunc: func(container string) (io.ReadCloser, error) {
|
||||||
|
return ioutil.NopCloser(strings.NewReader("bar")), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
cmd := NewExportCommand(cli)
|
||||||
|
cmd.SetOutput(ioutil.Discard)
|
||||||
|
cmd.SetArgs([]string{"-o", dir.Join("foo"), "container"})
|
||||||
|
assert.NilError(t, cmd.Execute())
|
||||||
|
|
||||||
|
expected := fs.Expected(t,
|
||||||
|
fs.WithFile("foo", "bar", fs.MatchAnyFileMode),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert.Assert(t, fs.Equal(dir.Path(), expected))
|
||||||
|
}
|
||||||
@@ -73,6 +73,6 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
|
|||||||
|
|
||||||
// RunPrune calls the Container Prune API
|
// RunPrune calls the Container Prune API
|
||||||
// This returns the amount of space reclaimed and a detailed output string
|
// This returns the amount of space reclaimed and a detailed output string
|
||||||
func RunPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
|
func RunPrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
return runPrune(dockerCli, pruneOptions{force: true, filter: filter})
|
return runPrune(dockerCli, pruneOptions{force: true, filter: filter})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// resizeTtyTo resizes tty to specific height and width
|
// resizeTtyTo resizes tty to specific height and width
|
||||||
func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id string, height, width uint, isExec bool) {
|
func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id string, height, width uint, isExec bool) error {
|
||||||
if height == 0 && width == 0 {
|
if height == 0 && width == 0 {
|
||||||
return
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
options := types.ResizeOptions{
|
options := types.ResizeOptions{
|
||||||
@@ -34,19 +34,42 @@ func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("Error resize: %s", err)
|
logrus.Debugf("Error resize: %s\r", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// resizeTty is to resize the tty with cli out's tty size
|
||||||
|
func resizeTty(ctx context.Context, cli command.Cli, id string, isExec bool) error {
|
||||||
|
height, width := cli.Out().GetTtySize()
|
||||||
|
return resizeTtyTo(ctx, cli.Client(), id, height, width, isExec)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initTtySize is to init the tty's size to the same as the window, if there is an error, it will retry 5 times.
|
||||||
|
func initTtySize(ctx context.Context, cli command.Cli, id string, isExec bool, resizeTtyFunc func(ctx context.Context, cli command.Cli, id string, isExec bool) error) {
|
||||||
|
rttyFunc := resizeTtyFunc
|
||||||
|
if rttyFunc == nil {
|
||||||
|
rttyFunc = resizeTty
|
||||||
|
}
|
||||||
|
if err := rttyFunc(ctx, cli, id, isExec); err != nil {
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
for retry := 0; retry < 5; retry++ {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
if err = rttyFunc(ctx, cli, id, isExec); err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(cli.Err(), "failed to resize tty, using default size")
|
||||||
|
}
|
||||||
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MonitorTtySize updates the container tty size when the terminal tty changes size
|
// MonitorTtySize updates the container tty size when the terminal tty changes size
|
||||||
func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool) error {
|
func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool) error {
|
||||||
resizeTty := func() {
|
initTtySize(ctx, cli, id, isExec, resizeTty)
|
||||||
height, width := cli.Out().GetTtySize()
|
|
||||||
resizeTtyTo(ctx, cli.Client(), id, height, width, isExec)
|
|
||||||
}
|
|
||||||
|
|
||||||
resizeTty()
|
|
||||||
|
|
||||||
if runtime.GOOS == "windows" {
|
if runtime.GOOS == "windows" {
|
||||||
go func() {
|
go func() {
|
||||||
prevH, prevW := cli.Out().GetTtySize()
|
prevH, prevW := cli.Out().GetTtySize()
|
||||||
@@ -55,7 +78,7 @@ func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool
|
|||||||
h, w := cli.Out().GetTtySize()
|
h, w := cli.Out().GetTtySize()
|
||||||
|
|
||||||
if prevW != w || prevH != h {
|
if prevW != w || prevH != h {
|
||||||
resizeTty()
|
resizeTty(ctx, cli, id, isExec)
|
||||||
}
|
}
|
||||||
prevH = h
|
prevH = h
|
||||||
prevW = w
|
prevW = w
|
||||||
@@ -66,7 +89,7 @@ func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool
|
|||||||
gosignal.Notify(sigchan, signal.SIGWINCH)
|
gosignal.Notify(sigchan, signal.SIGWINCH)
|
||||||
go func() {
|
go func() {
|
||||||
for range sigchan {
|
for range sigchan {
|
||||||
resizeTty()
|
resizeTty(ctx, cli, id, isExec)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package container
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/internal/test"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gotest.tools/assert"
|
||||||
|
is "gotest.tools/assert/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInitTtySizeErrors(t *testing.T) {
|
||||||
|
expectedError := "failed to resize tty, using default size\n"
|
||||||
|
fakeContainerExecResizeFunc := func(id string, options types.ResizeOptions) error {
|
||||||
|
return errors.Errorf("Error response from daemon: no such exec")
|
||||||
|
}
|
||||||
|
fakeResizeTtyFunc := func(ctx context.Context, cli command.Cli, id string, isExec bool) error {
|
||||||
|
height, width := uint(1024), uint(768)
|
||||||
|
return resizeTtyTo(ctx, cli.Client(), id, height, width, isExec)
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
cli := test.NewFakeCli(&fakeClient{containerExecResizeFunc: fakeContainerExecResizeFunc})
|
||||||
|
initTtySize(ctx, cli, "8mm8nn8tt8bb", true, fakeResizeTtyFunc)
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
assert.Check(t, is.Equal(expectedError, cli.ErrBuffer().String()))
|
||||||
|
}
|
||||||
@@ -3,11 +3,12 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
|
||||||
"github.com/docker/cli/internal/licenseutils"
|
"github.com/docker/cli/internal/licenseutils"
|
||||||
|
clitypes "github.com/docker/cli/types"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/licensing/model"
|
"github.com/docker/licensing/model"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -15,19 +16,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type activateOptions struct {
|
type activateOptions struct {
|
||||||
licenseFile string
|
licenseFile string
|
||||||
version string
|
version string
|
||||||
registryPrefix string
|
registryPrefix string
|
||||||
format string
|
format string
|
||||||
image string
|
image string
|
||||||
quiet bool
|
quiet bool
|
||||||
displayOnly bool
|
displayOnly bool
|
||||||
sockPath string
|
sockPath string
|
||||||
|
licenseLoginFunc func(ctx context.Context, authConfig *types.AuthConfig) (licenseutils.HubUser, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newActivateCommand creates a new `docker engine activate` command
|
// newActivateCommand creates a new `docker engine activate` command
|
||||||
func newActivateCommand(dockerCli command.Cli) *cobra.Command {
|
func newActivateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var options activateOptions
|
var options activateOptions
|
||||||
|
options.licenseLoginFunc = licenseutils.Login
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "activate [OPTIONS]",
|
Use: "activate [OPTIONS]",
|
||||||
@@ -56,10 +59,10 @@ https://hub.docker.com/ then specify the file with the '--license' flag.
|
|||||||
|
|
||||||
flags.StringVar(&options.licenseFile, "license", "", "License File")
|
flags.StringVar(&options.licenseFile, "license", "", "License File")
|
||||||
flags.StringVar(&options.version, "version", "", "Specify engine version (default is to use currently running version)")
|
flags.StringVar(&options.version, "version", "", "Specify engine version (default is to use currently running version)")
|
||||||
flags.StringVar(&options.registryPrefix, "registry-prefix", "docker.io/docker", "Override the default location where engine images are pulled")
|
flags.StringVar(&options.registryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the default location where engine images are pulled")
|
||||||
flags.StringVar(&options.image, "engine-image", containerizedengine.EnterpriseEngineImage, "Specify engine image")
|
flags.StringVar(&options.image, "engine-image", "", "Specify engine image")
|
||||||
flags.StringVar(&options.format, "format", "", "Pretty-print licenses using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print licenses using a Go template")
|
||||||
flags.BoolVar(&options.displayOnly, "display-only", false, "only display the available licenses and exit")
|
flags.BoolVar(&options.displayOnly, "display-only", false, "only display license information and exit")
|
||||||
flags.BoolVar(&options.quiet, "quiet", false, "Only display available licenses by ID")
|
flags.BoolVar(&options.quiet, "quiet", false, "Only display available licenses by ID")
|
||||||
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
|
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
|
||||||
|
|
||||||
@@ -67,6 +70,9 @@ https://hub.docker.com/ then specify the file with the '--license' flag.
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runActivate(cli command.Cli, options activateOptions) error {
|
func runActivate(cli command.Cli, options activateOptions) error {
|
||||||
|
if !isRoot() {
|
||||||
|
return errors.New("this command must be run as a privileged user")
|
||||||
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client, err := cli.NewContainerizedEngineClient(options.sockPath)
|
client, err := cli.NewContainerizedEngineClient(options.sockPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -94,26 +100,48 @@ func runActivate(cli command.Cli, options activateOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err = licenseutils.ApplyLicense(ctx, cli.Client(), license); err != nil {
|
summary, err := licenseutils.GetLicenseSummary(ctx, *license)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Fprintf(cli.Out(), "License: %s\n", summary)
|
||||||
|
if options.displayOnly {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dclient := cli.Client()
|
||||||
|
if err = licenseutils.ApplyLicense(ctx, dclient, license); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := containerizedengine.EngineInitOptions{
|
// Short circuit if the user didn't specify a version and we're already running enterprise
|
||||||
|
if options.version == "" {
|
||||||
|
serverVersion, err := dclient.ServerVersion(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if strings.Contains(strings.ToLower(serverVersion.Platform.Name), "enterprise") {
|
||||||
|
fmt.Fprintln(cli.Out(), "Successfully activated engine license on existing enterprise engine.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
options.version = serverVersion.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := clitypes.EngineInitOptions{
|
||||||
RegistryPrefix: options.registryPrefix,
|
RegistryPrefix: options.registryPrefix,
|
||||||
EngineImage: options.image,
|
EngineImage: options.image,
|
||||||
EngineVersion: options.version,
|
EngineVersion: options.version,
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.ActivateEngine(ctx, opts, cli.Out(), authConfig,
|
if err := client.ActivateEngine(ctx, opts, cli.Out(), authConfig); err != nil {
|
||||||
func(ctx context.Context) error {
|
return err
|
||||||
client := cli.Client()
|
}
|
||||||
_, err := client.Ping(ctx)
|
fmt.Fprintln(cli.Out(), `Successfully activated engine.
|
||||||
return err
|
Restart docker with 'systemctl restart docker' to complete the activation.`)
|
||||||
})
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLicenses(ctx context.Context, authConfig *types.AuthConfig, cli command.Cli, options activateOptions) (*model.IssuedLicense, error) {
|
func getLicenses(ctx context.Context, authConfig *types.AuthConfig, cli command.Cli, options activateOptions) (*model.IssuedLicense, error) {
|
||||||
user, err := licenseutils.Login(ctx, authConfig)
|
user, err := options.licenseLoginFunc(ctx, authConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,35 @@
|
|||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
"github.com/docker/cli/internal/licenseutils"
|
||||||
|
"github.com/docker/cli/internal/test"
|
||||||
|
clitypes "github.com/docker/cli/types"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/licensing"
|
||||||
|
"github.com/docker/licensing/model"
|
||||||
"gotest.tools/assert"
|
"gotest.tools/assert"
|
||||||
|
"gotest.tools/fs"
|
||||||
|
"gotest.tools/golden"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// nolint: lll
|
||||||
|
expiredLicense = `{"key_id":"irlYm3b9fdD8hMUXjazF39im7VQSSbAm9tfHK8cKUxJt","private_key":"aH5tTRDAVJpCRS2CRetTQVXIKgWUPfoCHODhDvNPvAbz","authorization":"ewogICAicGF5bG9hZCI6ICJleUpsZUhCcGNtRjBhVzl1SWpvaU1qQXhPQzB3TXkweE9GUXdOem93TURvd01Gb2lMQ0owYjJ0bGJpSTZJbkZtTVMxMlVtRmtialp5YjFaMldXdHJlVXN4VFdKMGNGUmpXR1ozVjA4MVRWZFFTM2cwUnpJd2NIYzlJaXdpYldGNFJXNW5hVzVsY3lJNk1Td2ljMk5oYm01cGJtZEZibUZpYkdWa0lqcDBjblZsTENKc2FXTmxibk5sVkhsd1pTSTZJazltWm14cGJtVWlMQ0owYVdWeUlqb2lVSEp2WkhWamRHbHZiaUo5IiwKICAgInNpZ25hdHVyZXMiOiBbCiAgICAgIHsKICAgICAgICAgImhlYWRlciI6IHsKICAgICAgICAgICAgImp3ayI6IHsKICAgICAgICAgICAgICAgImUiOiAiQVFBQiIsCiAgICAgICAgICAgICAgICJrZXlJRCI6ICJKN0xEOjY3VlI6TDVIWjpVN0JBOjJPNEc6NEFMMzpPRjJOOkpIR0I6RUZUSDo1Q1ZROk1GRU86QUVJVCIsCiAgICAgICAgICAgICAgICJraWQiOiAiSjdMRDo2N1ZSOkw1SFo6VTdCQToyTzRHOjRBTDM6T0YyTjpKSEdCOkVGVEg6NUNWUTpNRkVPOkFFSVQiLAogICAgICAgICAgICAgICAia3R5IjogIlJTQSIsCiAgICAgICAgICAgICAgICJuIjogInlkSXktbFU3bzdQY2VZLTQtcy1DUTVPRWdDeUY4Q3hJY1FJV3VLODRwSWlaY2lZNjczMHlDWW53TFNLVGx3LVU2VUNfUVJlV1Jpb01OTkU1RHM1VFlFWGJHRzZvbG0ycWRXYkJ3Y0NnLTJVVUhfT2NCOVd1UDZnUlBIcE1GTXN4RHpXd3ZheThKVXVIZ1lVTFVwbTFJdi1tcTdscDVuUV9SeHJUMEtaUkFRVFlMRU1FZkd3bTNoTU9fZ2VMUFMtaGdLUHRJSGxrZzZfV2NveFRHb0tQNzlkX3dhSFl4R05sN1doU25laUJTeGJwYlFBS2syMWxnNzk4WGI3dlp5RUFURE1yUlI5TWVFNkFkajVISnBZM0NveVJBUENtYUtHUkNLNHVvWlNvSXUwaEZWbEtVUHliYncwMDBHTy13YTJLTjhVd2dJSW0waTVJMXVXOUdrcTR6akJ5NXpoZ3F1VVhiRzliV1BBT1lycTVRYTgxRHhHY0JsSnlIWUFwLUREUEU5VEdnNHpZbVhqSm54WnFIRWR1R3FkZXZaOFhNSTB1a2ZrR0lJMTR3VU9pTUlJSXJYbEVjQmZfNDZJOGdRV0R6eHljWmVfSkdYLUxBdWF5WHJ5clVGZWhWTlVkWlVsOXdYTmFKQi1rYUNxejVRd2FSOTNzR3ctUVNmdEQwTnZMZTdDeU9ILUU2dmc2U3RfTmVUdmd2OFluaENpWElsWjhIT2ZJd05lN3RFRl9VY3o1T2JQeWttM3R5bHJOVWp0MFZ5QW10dGFjVkkyaUdpaGNVUHJtazRsVklaN1ZEX0xTVy1pN3lvU3VydHBzUFhjZTJwS0RJbzMwbEpHaE9fM0tVbWwyU1VaQ3F6SjF5RW1LcHlzSDVIRFc5Y3NJRkNBM2RlQWpmWlV2TjdVIgogICAgICAgICAgICB9LAogICAgICAgICAgICAiYWxnIjogIlJTMjU2IgogICAgICAgICB9LAogICAgICAgICAic2lnbmF0dXJlIjogIm5saTZIdzRrbW5KcTBSUmRXaGVfbkhZS2VJLVpKenM1U0d5SUpDakh1dWtnVzhBYklpVzFZYWJJR2NqWUt0QTY4dWN6T1hyUXZreGxWQXJLSlgzMDJzN0RpbzcxTlNPRzJVcnhsSjlibDFpd0F3a3ZyTEQ2T0p5MGxGLVg4WnRabXhPVmNQZmwzcmJwZFQ0dnlnWTdNcU1QRXdmb0IxTmlWZDYyZ1cxU2NSREZZcWw3R0FVaFVKNkp4QU15VzVaOXl5YVE0NV8wd0RMUk5mRjA5YWNXeVowTjRxVS1hZjhrUTZUUWZUX05ERzNCR3pRb2V3cHlEajRiMFBHb0diOFhLdDlwekpFdEdxM3lQM25VMFFBbk90a2gwTnZac1l1UFcyUnhDT3lRNEYzVlR3UkF2eF9HSTZrMVRpYmlKNnByUWluUy16Sjh6RE8zUjBuakE3OFBwNXcxcVpaUE9BdmtzZFNSYzJDcVMtcWhpTmF5YUhOVHpVNnpyOXlOZHR2S0o1QjNST0FmNUtjYXNiWURjTnVpeXBUNk90LUtqQ2I1dmYtWVpnc2FRNzJBdFBhSU4yeUpNREZHbmEwM0hpSjMxcTJRUlp5eTZrd3RYaGtwcDhTdEdIcHYxSWRaV09SVWttb0g5SFBzSGk4SExRLTZlM0tEY2x1RUQyMTNpZnljaVhtN0YzdHdaTTNHeDd1UXR1SldHaUlTZ2Z0QW9lVjZfUmI2VThkMmZxNzZuWHYxak5nckRRcE5waEZFd2tCdGRtZHZ2THByZVVYX3BWangza1AxN3pWbXFKNmNOOWkwWUc4WHg2VmRzcUxsRXUxQ2Rhd3Q0eko1M3VHMFlKTjRnUDZwc25yUS1uM0U1aFdlMDJ3d3dBZ3F3bGlPdmd4V1RTeXJyLXY2eDI0IiwKICAgICAgICAgInByb3RlY3RlZCI6ICJleUptYjNKdFlYUk1aVzVuZEdnaU9qRTNNeXdpWm05eWJXRjBWR0ZwYkNJNkltWlJJaXdpZEdsdFpTSTZJakl3TVRjdE1EVXRNRFZVTWpFNk5UYzZNek5hSW4wIgogICAgICB9CiAgIF0KfQ=="}`
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestActivateNoContainerd(t *testing.T) {
|
func TestActivateNoContainerd(t *testing.T) {
|
||||||
testCli.SetContainerizedEngineClient(
|
testCli.SetContainerizedEngineClient(
|
||||||
func(string) (containerizedengine.Client, error) {
|
func(string) (clitypes.ContainerizedClient, error) {
|
||||||
return nil, fmt.Errorf("some error")
|
return nil, fmt.Errorf("some error")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
isRoot = func() bool { return true }
|
||||||
cmd := newActivateCommand(testCli)
|
cmd := newActivateCommand(testCli)
|
||||||
cmd.Flags().Set("license", "invalidpath")
|
cmd.Flags().Set("license", "invalidpath")
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
@@ -24,10 +40,11 @@ func TestActivateNoContainerd(t *testing.T) {
|
|||||||
|
|
||||||
func TestActivateBadLicense(t *testing.T) {
|
func TestActivateBadLicense(t *testing.T) {
|
||||||
testCli.SetContainerizedEngineClient(
|
testCli.SetContainerizedEngineClient(
|
||||||
func(string) (containerizedengine.Client, error) {
|
func(string) (clitypes.ContainerizedClient, error) {
|
||||||
return &fakeContainerizedEngineClient{}, nil
|
return &fakeContainerizedEngineClient{}, nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
isRoot = func() bool { return true }
|
||||||
cmd := newActivateCommand(testCli)
|
cmd := newActivateCommand(testCli)
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
cmd.SilenceErrors = true
|
cmd.SilenceErrors = true
|
||||||
@@ -35,3 +52,95 @@ func TestActivateBadLicense(t *testing.T) {
|
|||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
assert.Error(t, err, "open invalidpath: no such file or directory")
|
assert.Error(t, err, "open invalidpath: no such file or directory")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestActivateExpiredLicenseDryRun(t *testing.T) {
|
||||||
|
dir := fs.NewDir(t, "license", fs.WithFile("docker.lic", expiredLicense, fs.WithMode(0644)))
|
||||||
|
defer dir.Remove()
|
||||||
|
filename := dir.Join("docker.lic")
|
||||||
|
isRoot = func() bool { return true }
|
||||||
|
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil, types.Info{}, nil})
|
||||||
|
c.SetContainerizedEngineClient(
|
||||||
|
func(string) (clitypes.ContainerizedClient, error) {
|
||||||
|
return &fakeContainerizedEngineClient{}, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
cmd := newActivateCommand(c)
|
||||||
|
cmd.SilenceUsage = true
|
||||||
|
cmd.SilenceErrors = true
|
||||||
|
cmd.Flags().Set("license", filename)
|
||||||
|
cmd.Flags().Set("display-only", "true")
|
||||||
|
c.OutBuffer().Reset()
|
||||||
|
err := cmd.Execute()
|
||||||
|
assert.NilError(t, err)
|
||||||
|
golden.Assert(t, c.OutBuffer().String(), "expired-license-display-only.golden")
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockLicenseClient struct{}
|
||||||
|
|
||||||
|
func (c mockLicenseClient) LoginViaAuth(ctx context.Context, username, password string) (authToken string, err error) {
|
||||||
|
return "", fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c mockLicenseClient) GetHubUserOrgs(ctx context.Context, authToken string) (orgs []model.Org, err error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
func (c mockLicenseClient) GetHubUserByName(ctx context.Context, username string) (user *model.User, err error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
func (c mockLicenseClient) VerifyLicense(ctx context.Context, license model.IssuedLicense) (res *model.CheckResponse, err error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
func (c mockLicenseClient) GenerateNewTrialSubscription(ctx context.Context, authToken, dockerID string) (subscriptionID string, err error) {
|
||||||
|
return "", fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
func (c mockLicenseClient) ListSubscriptions(ctx context.Context, authToken, dockerID string) (response []*model.Subscription, err error) {
|
||||||
|
expires := time.Date(2010, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
return []*model.Subscription{
|
||||||
|
{
|
||||||
|
State: "active",
|
||||||
|
Expires: &expires,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (c mockLicenseClient) ListSubscriptionsDetails(ctx context.Context, authToken, dockerID string) (response []*model.SubscriptionDetail, err error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
func (c mockLicenseClient) DownloadLicenseFromHub(ctx context.Context, authToken, subscriptionID string) (license *model.IssuedLicense, err error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
func (c mockLicenseClient) ParseLicense(license []byte) (parsedLicense *model.IssuedLicense, err error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
func (c mockLicenseClient) StoreLicense(ctx context.Context, dclnt licensing.WrappedDockerClient, licenses *model.IssuedLicense, localRootDir string) error {
|
||||||
|
return fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
func (c mockLicenseClient) LoadLocalLicense(ctx context.Context, dclnt licensing.WrappedDockerClient) (*model.Subscription, error) {
|
||||||
|
return nil, fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
func (c mockLicenseClient) SummarizeLicense(res *model.CheckResponse, keyID string) *model.Subscription {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func TestActivateDisplayOnlyHub(t *testing.T) {
|
||||||
|
isRoot = func() bool { return true }
|
||||||
|
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil, types.Info{}, nil})
|
||||||
|
c.SetContainerizedEngineClient(
|
||||||
|
func(string) (clitypes.ContainerizedClient, error) {
|
||||||
|
return &fakeContainerizedEngineClient{}, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
hubUser := licenseutils.HubUser{
|
||||||
|
Client: mockLicenseClient{},
|
||||||
|
}
|
||||||
|
options := activateOptions{
|
||||||
|
licenseLoginFunc: func(ctx context.Context, authConfig *types.AuthConfig) (licenseutils.HubUser, error) {
|
||||||
|
return hubUser, nil
|
||||||
|
},
|
||||||
|
displayOnly: true,
|
||||||
|
}
|
||||||
|
c.OutBuffer().Reset()
|
||||||
|
err := runActivate(c, options)
|
||||||
|
|
||||||
|
assert.NilError(t, err)
|
||||||
|
golden.Assert(t, c.OutBuffer().String(), "expired-hub-license-display-only.golden")
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
isRoot = func() bool {
|
||||||
|
return unix.Geteuid() == 0
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// +build windows
|
||||||
|
|
||||||
|
package engine
|
||||||
|
|
||||||
|
var (
|
||||||
|
isRoot = func() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/trust"
|
"github.com/docker/cli/cli/trust"
|
||||||
|
clitypes "github.com/docker/cli/types"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
@@ -13,7 +14,7 @@ import (
|
|||||||
|
|
||||||
func getRegistryAuth(cli command.Cli, registryPrefix string) (*types.AuthConfig, error) {
|
func getRegistryAuth(cli command.Cli, registryPrefix string) (*types.AuthConfig, error) {
|
||||||
if registryPrefix == "" {
|
if registryPrefix == "" {
|
||||||
registryPrefix = "docker.io/docker"
|
registryPrefix = clitypes.RegistryPrefix
|
||||||
}
|
}
|
||||||
distributionRef, err := reference.ParseNormalizedNamed(registryPrefix)
|
distributionRef, err := reference.ParseNormalizedNamed(registryPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
+25
-33
@@ -7,18 +7,16 @@ import (
|
|||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
"github.com/docker/cli/internal/versions"
|
||||||
|
clitypes "github.com/docker/cli/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
releaseNotePrefix = "https://docs.docker.com/releasenotes"
|
|
||||||
)
|
|
||||||
|
|
||||||
type checkOptions struct {
|
type checkOptions struct {
|
||||||
registryPrefix string
|
registryPrefix string
|
||||||
preReleases bool
|
preReleases bool
|
||||||
|
engineImage string
|
||||||
downgrades bool
|
downgrades bool
|
||||||
upgrades bool
|
upgrades bool
|
||||||
format string
|
format string
|
||||||
@@ -38,9 +36,10 @@ func newCheckForUpdatesCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVar(&options.registryPrefix, "registry-prefix", "", "Override the existing location where engine images are pulled")
|
flags.StringVar(&options.registryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the existing location where engine images are pulled")
|
||||||
flags.BoolVar(&options.downgrades, "downgrades", false, "Report downgrades (default omits older versions)")
|
flags.BoolVar(&options.downgrades, "downgrades", false, "Report downgrades (default omits older versions)")
|
||||||
flags.BoolVar(&options.preReleases, "pre-releases", false, "Include pre-release versions")
|
flags.BoolVar(&options.preReleases, "pre-releases", false, "Include pre-release versions")
|
||||||
|
flags.StringVar(&options.engineImage, "engine-image", "", "Specify engine image (default uses the same image as currently running)")
|
||||||
flags.BoolVar(&options.upgrades, "upgrades", true, "Report available upgrades")
|
flags.BoolVar(&options.upgrades, "upgrades", true, "Report available upgrades")
|
||||||
flags.StringVar(&options.format, "format", "", "Pretty-print updates using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print updates using a Go template")
|
||||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display available versions")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display available versions")
|
||||||
@@ -50,54 +49,47 @@ func newCheckForUpdatesCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runCheck(dockerCli command.Cli, options checkOptions) error {
|
func runCheck(dockerCli command.Cli, options checkOptions) error {
|
||||||
|
if !isRoot() {
|
||||||
|
return errors.New("this command must be run as a privileged user")
|
||||||
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
|
client := dockerCli.Client()
|
||||||
if err != nil {
|
serverVersion, err := client.ServerVersion(ctx)
|
||||||
return errors.Wrap(err, "unable to access local containerd")
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
currentOpts, err := client.GetCurrentEngineVersion(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// override with user provided prefix if specified
|
availVersions, err := versions.GetEngineVersions(ctx, dockerCli.RegistryClient(false), options.registryPrefix, options.engineImage, serverVersion.Version)
|
||||||
if options.registryPrefix != "" {
|
|
||||||
currentOpts.RegistryPrefix = options.registryPrefix
|
|
||||||
}
|
|
||||||
imageName := currentOpts.RegistryPrefix + "/" + currentOpts.EngineImage
|
|
||||||
currentVersion := currentOpts.EngineVersion
|
|
||||||
versions, err := client.GetEngineVersions(ctx, dockerCli.RegistryClient(false), currentVersion, imageName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
availUpdates := []containerizedengine.Update{
|
availUpdates := []clitypes.Update{
|
||||||
{Type: "current", Version: currentVersion},
|
{Type: "current", Version: serverVersion.Version},
|
||||||
}
|
}
|
||||||
if len(versions.Patches) > 0 {
|
if len(availVersions.Patches) > 0 {
|
||||||
availUpdates = append(availUpdates,
|
availUpdates = append(availUpdates,
|
||||||
processVersions(
|
processVersions(
|
||||||
currentVersion,
|
serverVersion.Version,
|
||||||
"patch",
|
"patch",
|
||||||
options.preReleases,
|
options.preReleases,
|
||||||
versions.Patches)...)
|
availVersions.Patches)...)
|
||||||
}
|
}
|
||||||
if options.upgrades {
|
if options.upgrades {
|
||||||
availUpdates = append(availUpdates,
|
availUpdates = append(availUpdates,
|
||||||
processVersions(
|
processVersions(
|
||||||
currentVersion,
|
serverVersion.Version,
|
||||||
"upgrade",
|
"upgrade",
|
||||||
options.preReleases,
|
options.preReleases,
|
||||||
versions.Upgrades)...)
|
availVersions.Upgrades)...)
|
||||||
}
|
}
|
||||||
if options.downgrades {
|
if options.downgrades {
|
||||||
availUpdates = append(availUpdates,
|
availUpdates = append(availUpdates,
|
||||||
processVersions(
|
processVersions(
|
||||||
currentVersion,
|
serverVersion.Version,
|
||||||
"downgrade",
|
"downgrade",
|
||||||
options.preReleases,
|
options.preReleases,
|
||||||
versions.Downgrades)...)
|
availVersions.Downgrades)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
format := options.format
|
format := options.format
|
||||||
@@ -115,17 +107,17 @@ func runCheck(dockerCli command.Cli, options checkOptions) error {
|
|||||||
|
|
||||||
func processVersions(currentVersion, verType string,
|
func processVersions(currentVersion, verType string,
|
||||||
includePrerelease bool,
|
includePrerelease bool,
|
||||||
versions []containerizedengine.DockerVersion) []containerizedengine.Update {
|
availVersions []clitypes.DockerVersion) []clitypes.Update {
|
||||||
availUpdates := []containerizedengine.Update{}
|
availUpdates := []clitypes.Update{}
|
||||||
for _, ver := range versions {
|
for _, ver := range availVersions {
|
||||||
if !includePrerelease && ver.Prerelease() != "" {
|
if !includePrerelease && ver.Prerelease() != "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if ver.Tag != currentVersion {
|
if ver.Tag != currentVersion {
|
||||||
availUpdates = append(availUpdates, containerizedengine.Update{
|
availUpdates = append(availUpdates, clitypes.Update{
|
||||||
Type: verType,
|
Type: verType,
|
||||||
Version: ver.Tag,
|
Version: ver.Tag,
|
||||||
Notes: fmt.Sprintf("%s/%s", releaseNotePrefix, ver.Tag),
|
Notes: fmt.Sprintf("%s?%s", clitypes.ReleaseNotePrefix, ver.Tag),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
registryclient "github.com/docker/cli/cli/registry/client"
|
manifesttypes "github.com/docker/cli/cli/manifest/types"
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
|
||||||
"github.com/docker/cli/internal/test"
|
"github.com/docker/cli/internal/test"
|
||||||
|
"github.com/docker/distribution"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
ver "github.com/hashicorp/go-version"
|
"github.com/opencontainers/go-digest"
|
||||||
"gotest.tools/assert"
|
"gotest.tools/assert"
|
||||||
"gotest.tools/golden"
|
"gotest.tools/golden"
|
||||||
)
|
)
|
||||||
@@ -18,126 +20,95 @@ var (
|
|||||||
testCli = test.NewFakeCli(&client.Client{})
|
testCli = test.NewFakeCli(&client.Client{})
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCheckForUpdatesNoContainerd(t *testing.T) {
|
type verClient struct {
|
||||||
testCli.SetContainerizedEngineClient(
|
client.Client
|
||||||
func(string) (containerizedengine.Client, error) {
|
ver types.Version
|
||||||
return nil, fmt.Errorf("some error")
|
verErr error
|
||||||
},
|
info types.Info
|
||||||
)
|
infoErr error
|
||||||
cmd := newCheckForUpdatesCommand(testCli)
|
}
|
||||||
cmd.SilenceUsage = true
|
|
||||||
cmd.SilenceErrors = true
|
func (c *verClient) ServerVersion(ctx context.Context) (types.Version, error) {
|
||||||
err := cmd.Execute()
|
return c.ver, c.verErr
|
||||||
assert.ErrorContains(t, err, "unable to access local containerd")
|
}
|
||||||
|
|
||||||
|
func (c *verClient) Info(ctx context.Context) (types.Info, error) {
|
||||||
|
return c.info, c.infoErr
|
||||||
|
}
|
||||||
|
|
||||||
|
type testRegistryClient struct {
|
||||||
|
tags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c testRegistryClient) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
|
||||||
|
return manifesttypes.ImageManifest{}, nil
|
||||||
|
}
|
||||||
|
func (c testRegistryClient) GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (c testRegistryClient) MountBlob(ctx context.Context, source reference.Canonical, target reference.Named) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c testRegistryClient) PutManifest(ctx context.Context, ref reference.Named, manifest distribution.Manifest) (digest.Digest, error) {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
func (c testRegistryClient) GetTags(ctx context.Context, ref reference.Named) ([]string, error) {
|
||||||
|
return c.tags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckForUpdatesNoCurrentVersion(t *testing.T) {
|
func TestCheckForUpdatesNoCurrentVersion(t *testing.T) {
|
||||||
retErr := fmt.Errorf("some failure")
|
isRoot = func() bool { return true }
|
||||||
getCurrentEngineVersionFunc := func(ctx context.Context) (containerizedengine.EngineInitOptions, error) {
|
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{}, nil, types.Info{}, nil})
|
||||||
return containerizedengine.EngineInitOptions{}, retErr
|
c.SetRegistryClient(testRegistryClient{})
|
||||||
}
|
cmd := newCheckForUpdatesCommand(c)
|
||||||
testCli.SetContainerizedEngineClient(
|
|
||||||
func(string) (containerizedengine.Client, error) {
|
|
||||||
return &fakeContainerizedEngineClient{
|
|
||||||
getCurrentEngineVersionFunc: getCurrentEngineVersionFunc,
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
cmd := newCheckForUpdatesCommand(testCli)
|
|
||||||
cmd.SilenceUsage = true
|
cmd.SilenceUsage = true
|
||||||
cmd.SilenceErrors = true
|
cmd.SilenceErrors = true
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
assert.Assert(t, err == retErr)
|
assert.ErrorContains(t, err, "no such file or directory")
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckForUpdatesGetEngineVersionsFail(t *testing.T) {
|
|
||||||
retErr := fmt.Errorf("some failure")
|
|
||||||
getEngineVersionsFunc := func(ctx context.Context,
|
|
||||||
registryClient registryclient.RegistryClient,
|
|
||||||
currentVersion, imageName string) (containerizedengine.AvailableVersions, error) {
|
|
||||||
return containerizedengine.AvailableVersions{}, retErr
|
|
||||||
}
|
|
||||||
testCli.SetContainerizedEngineClient(
|
|
||||||
func(string) (containerizedengine.Client, error) {
|
|
||||||
return &fakeContainerizedEngineClient{
|
|
||||||
getEngineVersionsFunc: getEngineVersionsFunc,
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
cmd := newCheckForUpdatesCommand(testCli)
|
|
||||||
cmd.SilenceUsage = true
|
|
||||||
cmd.SilenceErrors = true
|
|
||||||
err := cmd.Execute()
|
|
||||||
assert.Assert(t, err == retErr)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckForUpdatesGetEngineVersionsHappy(t *testing.T) {
|
func TestCheckForUpdatesGetEngineVersionsHappy(t *testing.T) {
|
||||||
getCurrentEngineVersionFunc := func(ctx context.Context) (containerizedengine.EngineInitOptions, error) {
|
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{Version: "1.1.0"}, nil, types.Info{ServerVersion: "1.1.0"}, nil})
|
||||||
return containerizedengine.EngineInitOptions{
|
c.SetRegistryClient(testRegistryClient{[]string{
|
||||||
EngineImage: "current engine",
|
"1.0.1", "1.0.2", "1.0.3-beta1",
|
||||||
EngineVersion: "1.1.0",
|
"1.1.1", "1.1.2", "1.1.3-beta1",
|
||||||
}, nil
|
"1.2.0", "2.0.0", "2.1.0-beta1",
|
||||||
}
|
}})
|
||||||
getEngineVersionsFunc := func(ctx context.Context,
|
|
||||||
registryClient registryclient.RegistryClient,
|
isRoot = func() bool { return true }
|
||||||
currentVersion, imageName string) (containerizedengine.AvailableVersions, error) {
|
cmd := newCheckForUpdatesCommand(c)
|
||||||
return containerizedengine.AvailableVersions{
|
|
||||||
Downgrades: parseVersions(t, "1.0.1", "1.0.2", "1.0.3-beta1"),
|
|
||||||
Patches: parseVersions(t, "1.1.1", "1.1.2", "1.1.3-beta1"),
|
|
||||||
Upgrades: parseVersions(t, "1.2.0", "2.0.0", "2.1.0-beta1"),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
testCli.SetContainerizedEngineClient(
|
|
||||||
func(string) (containerizedengine.Client, error) {
|
|
||||||
return &fakeContainerizedEngineClient{
|
|
||||||
getEngineVersionsFunc: getEngineVersionsFunc,
|
|
||||||
getCurrentEngineVersionFunc: getCurrentEngineVersionFunc,
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
cmd := newCheckForUpdatesCommand(testCli)
|
|
||||||
cmd.Flags().Set("pre-releases", "true")
|
cmd.Flags().Set("pre-releases", "true")
|
||||||
cmd.Flags().Set("downgrades", "true")
|
cmd.Flags().Set("downgrades", "true")
|
||||||
|
cmd.Flags().Set("engine-image", "engine-community")
|
||||||
|
cmd.SilenceUsage = true
|
||||||
|
cmd.SilenceErrors = true
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
golden.Assert(t, testCli.OutBuffer().String(), "check-all.golden")
|
golden.Assert(t, c.OutBuffer().String(), "check-all.golden")
|
||||||
|
|
||||||
testCli.OutBuffer().Reset()
|
c.OutBuffer().Reset()
|
||||||
cmd.Flags().Set("pre-releases", "false")
|
cmd.Flags().Set("pre-releases", "false")
|
||||||
cmd.Flags().Set("downgrades", "true")
|
cmd.Flags().Set("downgrades", "true")
|
||||||
err = cmd.Execute()
|
err = cmd.Execute()
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
fmt.Println(testCli.OutBuffer().String())
|
fmt.Println(c.OutBuffer().String())
|
||||||
golden.Assert(t, testCli.OutBuffer().String(), "check-no-prerelease.golden")
|
golden.Assert(t, c.OutBuffer().String(), "check-no-prerelease.golden")
|
||||||
|
|
||||||
testCli.OutBuffer().Reset()
|
c.OutBuffer().Reset()
|
||||||
cmd.Flags().Set("pre-releases", "false")
|
cmd.Flags().Set("pre-releases", "false")
|
||||||
cmd.Flags().Set("downgrades", "false")
|
cmd.Flags().Set("downgrades", "false")
|
||||||
err = cmd.Execute()
|
err = cmd.Execute()
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
fmt.Println(testCli.OutBuffer().String())
|
fmt.Println(c.OutBuffer().String())
|
||||||
golden.Assert(t, testCli.OutBuffer().String(), "check-no-downgrades.golden")
|
golden.Assert(t, c.OutBuffer().String(), "check-no-downgrades.golden")
|
||||||
|
|
||||||
testCli.OutBuffer().Reset()
|
c.OutBuffer().Reset()
|
||||||
cmd.Flags().Set("pre-releases", "false")
|
cmd.Flags().Set("pre-releases", "false")
|
||||||
cmd.Flags().Set("downgrades", "false")
|
cmd.Flags().Set("downgrades", "false")
|
||||||
cmd.Flags().Set("upgrades", "false")
|
cmd.Flags().Set("upgrades", "false")
|
||||||
err = cmd.Execute()
|
err = cmd.Execute()
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
fmt.Println(testCli.OutBuffer().String())
|
fmt.Println(c.OutBuffer().String())
|
||||||
golden.Assert(t, testCli.OutBuffer().String(), "check-patches-only.golden")
|
golden.Assert(t, c.OutBuffer().String(), "check-patches-only.golden")
|
||||||
}
|
|
||||||
|
|
||||||
func makeVersion(t *testing.T, tag string) containerizedengine.DockerVersion {
|
|
||||||
v, err := ver.NewVersion(tag)
|
|
||||||
assert.NilError(t, err)
|
|
||||||
return containerizedengine.DockerVersion{Version: *v, Tag: tag}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseVersions(t *testing.T, tags ...string) []containerizedengine.DockerVersion {
|
|
||||||
ret := make([]containerizedengine.DockerVersion, len(tags))
|
|
||||||
for i, tag := range tags {
|
|
||||||
ret[i] = makeVersion(t, tag)
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
|
|
||||||
"github.com/containerd/containerd"
|
"github.com/containerd/containerd"
|
||||||
registryclient "github.com/docker/cli/cli/registry/client"
|
registryclient "github.com/docker/cli/cli/registry/client"
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
clitypes "github.com/docker/cli/types"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -13,28 +13,26 @@ type (
|
|||||||
fakeContainerizedEngineClient struct {
|
fakeContainerizedEngineClient struct {
|
||||||
closeFunc func() error
|
closeFunc func() error
|
||||||
activateEngineFunc func(ctx context.Context,
|
activateEngineFunc func(ctx context.Context,
|
||||||
opts containerizedengine.EngineInitOptions,
|
opts clitypes.EngineInitOptions,
|
||||||
out containerizedengine.OutStream,
|
out clitypes.OutStream,
|
||||||
authConfig *types.AuthConfig,
|
authConfig *types.AuthConfig) error
|
||||||
healthfn func(context.Context) error) error
|
|
||||||
initEngineFunc func(ctx context.Context,
|
initEngineFunc func(ctx context.Context,
|
||||||
opts containerizedengine.EngineInitOptions,
|
opts clitypes.EngineInitOptions,
|
||||||
out containerizedengine.OutStream,
|
out clitypes.OutStream,
|
||||||
authConfig *types.AuthConfig,
|
authConfig *types.AuthConfig,
|
||||||
healthfn func(context.Context) error) error
|
healthfn func(context.Context) error) error
|
||||||
doUpdateFunc func(ctx context.Context,
|
doUpdateFunc func(ctx context.Context,
|
||||||
opts containerizedengine.EngineInitOptions,
|
opts clitypes.EngineInitOptions,
|
||||||
out containerizedengine.OutStream,
|
out clitypes.OutStream,
|
||||||
authConfig *types.AuthConfig,
|
authConfig *types.AuthConfig) error
|
||||||
healthfn func(context.Context) error) error
|
|
||||||
getEngineVersionsFunc func(ctx context.Context,
|
getEngineVersionsFunc func(ctx context.Context,
|
||||||
registryClient registryclient.RegistryClient,
|
registryClient registryclient.RegistryClient,
|
||||||
currentVersion,
|
currentVersion,
|
||||||
imageName string) (containerizedengine.AvailableVersions, error)
|
imageName string) (clitypes.AvailableVersions, error)
|
||||||
|
|
||||||
getEngineFunc func(ctx context.Context) (containerd.Container, error)
|
getEngineFunc func(ctx context.Context) (containerd.Container, error)
|
||||||
removeEngineFunc func(ctx context.Context, engine containerd.Container) error
|
removeEngineFunc func(ctx context.Context) error
|
||||||
getCurrentEngineVersionFunc func(ctx context.Context) (containerizedengine.EngineInitOptions, error)
|
getCurrentEngineVersionFunc func(ctx context.Context) (clitypes.EngineInitOptions, error)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,18 +44,17 @@ func (w *fakeContainerizedEngineClient) Close() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w *fakeContainerizedEngineClient) ActivateEngine(ctx context.Context,
|
func (w *fakeContainerizedEngineClient) ActivateEngine(ctx context.Context,
|
||||||
opts containerizedengine.EngineInitOptions,
|
opts clitypes.EngineInitOptions,
|
||||||
out containerizedengine.OutStream,
|
out clitypes.OutStream,
|
||||||
authConfig *types.AuthConfig,
|
authConfig *types.AuthConfig) error {
|
||||||
healthfn func(context.Context) error) error {
|
|
||||||
if w.activateEngineFunc != nil {
|
if w.activateEngineFunc != nil {
|
||||||
return w.activateEngineFunc(ctx, opts, out, authConfig, healthfn)
|
return w.activateEngineFunc(ctx, opts, out, authConfig)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (w *fakeContainerizedEngineClient) InitEngine(ctx context.Context,
|
func (w *fakeContainerizedEngineClient) InitEngine(ctx context.Context,
|
||||||
opts containerizedengine.EngineInitOptions,
|
opts clitypes.EngineInitOptions,
|
||||||
out containerizedengine.OutStream,
|
out clitypes.OutStream,
|
||||||
authConfig *types.AuthConfig,
|
authConfig *types.AuthConfig,
|
||||||
healthfn func(context.Context) error) error {
|
healthfn func(context.Context) error) error {
|
||||||
if w.initEngineFunc != nil {
|
if w.initEngineFunc != nil {
|
||||||
@@ -66,23 +63,22 @@ func (w *fakeContainerizedEngineClient) InitEngine(ctx context.Context,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (w *fakeContainerizedEngineClient) DoUpdate(ctx context.Context,
|
func (w *fakeContainerizedEngineClient) DoUpdate(ctx context.Context,
|
||||||
opts containerizedengine.EngineInitOptions,
|
opts clitypes.EngineInitOptions,
|
||||||
out containerizedengine.OutStream,
|
out clitypes.OutStream,
|
||||||
authConfig *types.AuthConfig,
|
authConfig *types.AuthConfig) error {
|
||||||
healthfn func(context.Context) error) error {
|
|
||||||
if w.doUpdateFunc != nil {
|
if w.doUpdateFunc != nil {
|
||||||
return w.doUpdateFunc(ctx, opts, out, authConfig, healthfn)
|
return w.doUpdateFunc(ctx, opts, out, authConfig)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (w *fakeContainerizedEngineClient) GetEngineVersions(ctx context.Context,
|
func (w *fakeContainerizedEngineClient) GetEngineVersions(ctx context.Context,
|
||||||
registryClient registryclient.RegistryClient,
|
registryClient registryclient.RegistryClient,
|
||||||
currentVersion, imageName string) (containerizedengine.AvailableVersions, error) {
|
currentVersion, imageName string) (clitypes.AvailableVersions, error) {
|
||||||
|
|
||||||
if w.getEngineVersionsFunc != nil {
|
if w.getEngineVersionsFunc != nil {
|
||||||
return w.getEngineVersionsFunc(ctx, registryClient, currentVersion, imageName)
|
return w.getEngineVersionsFunc(ctx, registryClient, currentVersion, imageName)
|
||||||
}
|
}
|
||||||
return containerizedengine.AvailableVersions{}, nil
|
return clitypes.AvailableVersions{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *fakeContainerizedEngineClient) GetEngine(ctx context.Context) (containerd.Container, error) {
|
func (w *fakeContainerizedEngineClient) GetEngine(ctx context.Context) (containerd.Container, error) {
|
||||||
@@ -91,15 +87,15 @@ func (w *fakeContainerizedEngineClient) GetEngine(ctx context.Context) (containe
|
|||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (w *fakeContainerizedEngineClient) RemoveEngine(ctx context.Context, engine containerd.Container) error {
|
func (w *fakeContainerizedEngineClient) RemoveEngine(ctx context.Context) error {
|
||||||
if w.removeEngineFunc != nil {
|
if w.removeEngineFunc != nil {
|
||||||
return w.removeEngineFunc(ctx, engine)
|
return w.removeEngineFunc(ctx)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (w *fakeContainerizedEngineClient) GetCurrentEngineVersion(ctx context.Context) (containerizedengine.EngineInitOptions, error) {
|
func (w *fakeContainerizedEngineClient) GetCurrentEngineVersion(ctx context.Context) (clitypes.EngineInitOptions, error) {
|
||||||
if w.getCurrentEngineVersionFunc != nil {
|
if w.getCurrentEngineVersionFunc != nil {
|
||||||
return w.getCurrentEngineVersionFunc(ctx)
|
return w.getCurrentEngineVersionFunc(ctx)
|
||||||
}
|
}
|
||||||
return containerizedengine.EngineInitOptions{}, nil
|
return clitypes.EngineInitOptions{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,11 +15,9 @@ func NewEngineCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
RunE: command.ShowHelp(dockerCli.Err()),
|
RunE: command.ShowHelp(dockerCli.Err()),
|
||||||
}
|
}
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
newInitCommand(dockerCli),
|
|
||||||
newActivateCommand(dockerCli),
|
newActivateCommand(dockerCli),
|
||||||
newCheckForUpdatesCommand(dockerCli),
|
newCheckForUpdatesCommand(dockerCli),
|
||||||
newUpdateCommand(dockerCli),
|
newUpdateCommand(dockerCli),
|
||||||
newRmCommand(dockerCli),
|
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,5 +10,5 @@ func TestNewEngineCommand(t *testing.T) {
|
|||||||
cmd := NewEngineCommand(testCli)
|
cmd := NewEngineCommand(testCli)
|
||||||
|
|
||||||
subcommands := cmd.Commands()
|
subcommands := cmd.Commands()
|
||||||
assert.Assert(t, len(subcommands) == 5)
|
assert.Assert(t, len(subcommands) == 3)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +1,10 @@
|
|||||||
package engine
|
package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
clitypes "github.com/docker/cli/types"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type extendedEngineInitOptions struct {
|
type extendedEngineInitOptions struct {
|
||||||
containerizedengine.EngineInitOptions
|
clitypes.EngineInitOptions
|
||||||
sockPath string
|
sockPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInitCommand(dockerCli command.Cli) *cobra.Command {
|
|
||||||
var options extendedEngineInitOptions
|
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "init [OPTIONS]",
|
|
||||||
Short: "Initialize a local engine",
|
|
||||||
Long: `This command will initialize a local engine running on containerd.
|
|
||||||
|
|
||||||
Configuration of the engine is managed through the daemon.json configuration
|
|
||||||
file on the host and may be pre-created before running the 'init' command.
|
|
||||||
`,
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runInit(dockerCli, options)
|
|
||||||
},
|
|
||||||
Annotations: map[string]string{"experimentalCLI": ""},
|
|
||||||
}
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVar(&options.EngineVersion, "version", cli.Version, "Specify engine version")
|
|
||||||
flags.StringVar(&options.EngineImage, "engine-image", containerizedengine.CommunityEngineImage, "Specify engine image")
|
|
||||||
flags.StringVar(&options.RegistryPrefix, "registry-prefix", "docker.io/docker", "Override the default location where engine images are pulled")
|
|
||||||
flags.StringVar(&options.ConfigFile, "config-file", "/etc/docker/daemon.json", "Specify the location of the daemon configuration file on the host")
|
|
||||||
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runInit(dockerCli command.Cli, options extendedEngineInitOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "unable to access local containerd")
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return client.InitEngine(ctx, options.EngineInitOptions, dockerCli.Out(), authConfig,
|
|
||||||
func(ctx context.Context) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
_, err := client.Ping(ctx)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
package engine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
|
||||||
"gotest.tools/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInitNoContainerd(t *testing.T) {
|
|
||||||
testCli.SetContainerizedEngineClient(
|
|
||||||
func(string) (containerizedengine.Client, error) {
|
|
||||||
return nil, fmt.Errorf("some error")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
cmd := newInitCommand(testCli)
|
|
||||||
cmd.SilenceUsage = true
|
|
||||||
cmd.SilenceErrors = true
|
|
||||||
err := cmd.Execute()
|
|
||||||
assert.ErrorContains(t, err, "unable to access local containerd")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInitHappy(t *testing.T) {
|
|
||||||
testCli.SetContainerizedEngineClient(
|
|
||||||
func(string) (containerizedengine.Client, error) {
|
|
||||||
return &fakeContainerizedEngineClient{}, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
cmd := newInitCommand(testCli)
|
|
||||||
err := cmd.Execute()
|
|
||||||
assert.NilError(t, err)
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package engine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TODO - consider adding a "purge" flag that also removes
|
|
||||||
// configuration files and the docker root dir.
|
|
||||||
|
|
||||||
type rmOptions struct {
|
|
||||||
sockPath string
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRmCommand(dockerCli command.Cli) *cobra.Command {
|
|
||||||
var options rmOptions
|
|
||||||
cmd := &cobra.Command{
|
|
||||||
Use: "rm [OPTIONS]",
|
|
||||||
Short: "Remove the local engine",
|
|
||||||
Long: `This command will remove the local engine running on containerd.
|
|
||||||
|
|
||||||
No state files will be removed from the host filesystem.
|
|
||||||
`,
|
|
||||||
Args: cli.NoArgs,
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
|
||||||
return runRm(dockerCli, options)
|
|
||||||
},
|
|
||||||
Annotations: map[string]string{"experimentalCLI": ""},
|
|
||||||
}
|
|
||||||
flags := cmd.Flags()
|
|
||||||
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func runRm(dockerCli command.Cli, options rmOptions) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "unable to access local containerd")
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
engine, err := client.GetEngine(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.RemoveEngine(ctx, engine)
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package engine
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
|
||||||
"gotest.tools/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRmNoContainerd(t *testing.T) {
|
|
||||||
testCli.SetContainerizedEngineClient(
|
|
||||||
func(string) (containerizedengine.Client, error) {
|
|
||||||
return nil, fmt.Errorf("some error")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
cmd := newRmCommand(testCli)
|
|
||||||
cmd.SilenceUsage = true
|
|
||||||
cmd.SilenceErrors = true
|
|
||||||
err := cmd.Execute()
|
|
||||||
assert.ErrorContains(t, err, "unable to access local containerd")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRmHappy(t *testing.T) {
|
|
||||||
testCli.SetContainerizedEngineClient(
|
|
||||||
func(string) (containerizedengine.Client, error) {
|
|
||||||
return &fakeContainerizedEngineClient{}, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
cmd := newRmCommand(testCli)
|
|
||||||
err := cmd.Execute()
|
|
||||||
assert.NilError(t, err)
|
|
||||||
}
|
|
||||||
+9
-9
@@ -1,11 +1,11 @@
|
|||||||
TYPE VERSION NOTES
|
TYPE VERSION NOTES
|
||||||
current 1.1.0
|
current 1.1.0
|
||||||
patch 1.1.1 https://docs.docker.com/releasenotes/1.1.1
|
patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
|
||||||
patch 1.1.2 https://docs.docker.com/releasenotes/1.1.2
|
patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2
|
||||||
patch 1.1.3-beta1 https://docs.docker.com/releasenotes/1.1.3-beta1
|
patch 1.1.3-beta1 https://docker.com/engine/releasenotes?1.1.3-beta1
|
||||||
upgrade 1.2.0 https://docs.docker.com/releasenotes/1.2.0
|
upgrade 1.2.0 https://docker.com/engine/releasenotes?1.2.0
|
||||||
upgrade 2.0.0 https://docs.docker.com/releasenotes/2.0.0
|
upgrade 2.0.0 https://docker.com/engine/releasenotes?2.0.0
|
||||||
upgrade 2.1.0-beta1 https://docs.docker.com/releasenotes/2.1.0-beta1
|
upgrade 2.1.0-beta1 https://docker.com/engine/releasenotes?2.1.0-beta1
|
||||||
downgrade 1.0.1 https://docs.docker.com/releasenotes/1.0.1
|
downgrade 1.0.1 https://docker.com/engine/releasenotes?1.0.1
|
||||||
downgrade 1.0.2 https://docs.docker.com/releasenotes/1.0.2
|
downgrade 1.0.2 https://docker.com/engine/releasenotes?1.0.2
|
||||||
downgrade 1.0.3-beta1 https://docs.docker.com/releasenotes/1.0.3-beta1
|
downgrade 1.0.3-beta1 https://docker.com/engine/releasenotes?1.0.3-beta1
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
TYPE VERSION NOTES
|
TYPE VERSION NOTES
|
||||||
current 1.1.0
|
current 1.1.0
|
||||||
patch 1.1.1 https://docs.docker.com/releasenotes/1.1.1
|
patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
|
||||||
patch 1.1.2 https://docs.docker.com/releasenotes/1.1.2
|
patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2
|
||||||
upgrade 1.2.0 https://docs.docker.com/releasenotes/1.2.0
|
upgrade 1.2.0 https://docker.com/engine/releasenotes?1.2.0
|
||||||
upgrade 2.0.0 https://docs.docker.com/releasenotes/2.0.0
|
upgrade 2.0.0 https://docker.com/engine/releasenotes?2.0.0
|
||||||
|
|||||||
+6
-6
@@ -1,8 +1,8 @@
|
|||||||
TYPE VERSION NOTES
|
TYPE VERSION NOTES
|
||||||
current 1.1.0
|
current 1.1.0
|
||||||
patch 1.1.1 https://docs.docker.com/releasenotes/1.1.1
|
patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
|
||||||
patch 1.1.2 https://docs.docker.com/releasenotes/1.1.2
|
patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2
|
||||||
upgrade 1.2.0 https://docs.docker.com/releasenotes/1.2.0
|
upgrade 1.2.0 https://docker.com/engine/releasenotes?1.2.0
|
||||||
upgrade 2.0.0 https://docs.docker.com/releasenotes/2.0.0
|
upgrade 2.0.0 https://docker.com/engine/releasenotes?2.0.0
|
||||||
downgrade 1.0.1 https://docs.docker.com/releasenotes/1.0.1
|
downgrade 1.0.1 https://docker.com/engine/releasenotes?1.0.1
|
||||||
downgrade 1.0.2 https://docs.docker.com/releasenotes/1.0.2
|
downgrade 1.0.2 https://docker.com/engine/releasenotes?1.0.2
|
||||||
|
|||||||
+2
-2
@@ -1,4 +1,4 @@
|
|||||||
TYPE VERSION NOTES
|
TYPE VERSION NOTES
|
||||||
current 1.1.0
|
current 1.1.0
|
||||||
patch 1.1.1 https://docs.docker.com/releasenotes/1.1.1
|
patch 1.1.1 https://docker.com/engine/releasenotes?1.1.1
|
||||||
patch 1.1.2 https://docs.docker.com/releasenotes/1.1.2
|
patch 1.1.2 https://docker.com/engine/releasenotes?1.1.2
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
Looking for existing licenses for ...
|
||||||
|
NUM OWNER PRODUCT ID EXPIRES PRICING COMPONENTS
|
||||||
|
0 2010-01-01 00:00:00 +0000 UTC
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
License: Quantity: 1 Nodes Expiration date: 2018-03-18 Expired! You will no longer receive updates. Please renew at https://docker.com/licensing
|
||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
clitypes "github.com/docker/cli/types"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -24,45 +25,31 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.StringVar(&options.EngineVersion, "version", "", "Specify engine version")
|
flags.StringVar(&options.EngineVersion, "version", "", "Specify engine version")
|
||||||
flags.StringVar(&options.EngineImage, "engine-image", "", "Specify engine image")
|
flags.StringVar(&options.EngineImage, "engine-image", "", "Specify engine image (default uses the same image as currently running)")
|
||||||
flags.StringVar(&options.RegistryPrefix, "registry-prefix", "", "Override the current location where engine images are pulled")
|
flags.StringVar(&options.RegistryPrefix, "registry-prefix", clitypes.RegistryPrefix, "Override the current location where engine images are pulled")
|
||||||
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
|
flags.StringVar(&options.sockPath, "containerd", "", "override default location of containerd endpoint")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error {
|
func runUpdate(dockerCli command.Cli, options extendedEngineInitOptions) error {
|
||||||
|
if !isRoot() {
|
||||||
|
return errors.New("this command must be run as a privileged user")
|
||||||
|
}
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
|
client, err := dockerCli.NewContainerizedEngineClient(options.sockPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "unable to access local containerd")
|
return errors.Wrap(err, "unable to access local containerd")
|
||||||
}
|
}
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
if options.EngineImage == "" || options.RegistryPrefix == "" {
|
|
||||||
currentOpts, err := client.GetCurrentEngineVersion(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if options.EngineImage == "" {
|
|
||||||
options.EngineImage = currentOpts.EngineImage
|
|
||||||
}
|
|
||||||
if options.RegistryPrefix == "" {
|
|
||||||
options.RegistryPrefix = currentOpts.RegistryPrefix
|
|
||||||
}
|
|
||||||
}
|
|
||||||
authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix)
|
authConfig, err := getRegistryAuth(dockerCli, options.RegistryPrefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := client.DoUpdate(ctx, options.EngineInitOptions, dockerCli.Out(), authConfig); err != nil {
|
||||||
if err := client.DoUpdate(ctx, options.EngineInitOptions, dockerCli.Out(), authConfig,
|
|
||||||
func(ctx context.Context) error {
|
|
||||||
client := dockerCli.Client()
|
|
||||||
_, err := client.Ping(ctx)
|
|
||||||
return err
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Fprintln(dockerCli.Out(), "Success! The docker engine is now running.")
|
fmt.Fprintln(dockerCli.Out(), `Successfully updated engine.
|
||||||
|
Restart docker with 'systemctl restart docker' to complete the update.`)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,13 +4,16 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
"github.com/docker/cli/internal/test"
|
||||||
|
clitypes "github.com/docker/cli/types"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
"gotest.tools/assert"
|
"gotest.tools/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUpdateNoContainerd(t *testing.T) {
|
func TestUpdateNoContainerd(t *testing.T) {
|
||||||
testCli.SetContainerizedEngineClient(
|
testCli.SetContainerizedEngineClient(
|
||||||
func(string) (containerizedengine.Client, error) {
|
func(string) (clitypes.ContainerizedClient, error) {
|
||||||
return nil, fmt.Errorf("some error")
|
return nil, fmt.Errorf("some error")
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -22,14 +25,16 @@ func TestUpdateNoContainerd(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateHappy(t *testing.T) {
|
func TestUpdateHappy(t *testing.T) {
|
||||||
testCli.SetContainerizedEngineClient(
|
c := test.NewFakeCli(&verClient{client.Client{}, types.Version{Version: "1.1.0"}, nil, types.Info{ServerVersion: "1.1.0"}, nil})
|
||||||
func(string) (containerizedengine.Client, error) {
|
c.SetContainerizedEngineClient(
|
||||||
|
func(string) (clitypes.ContainerizedClient, error) {
|
||||||
return &fakeContainerizedEngineClient{}, nil
|
return &fakeContainerizedEngineClient{}, nil
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
cmd := newUpdateCommand(testCli)
|
cmd := newUpdateCommand(c)
|
||||||
cmd.Flags().Set("registry-prefix", "docker.io/docker")
|
cmd.Flags().Set("registry-prefix", clitypes.RegistryPrefix)
|
||||||
cmd.Flags().Set("version", "someversion")
|
cmd.Flags().Set("version", "someversion")
|
||||||
|
cmd.Flags().Set("engine-image", "someimage")
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package formatter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/pkg/stringid"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultBuildCacheTableFormat = "table {{.ID}}\t{{.Type}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}\t{{.Description}}"
|
||||||
|
|
||||||
|
cacheIDHeader = "CACHE ID"
|
||||||
|
cacheTypeHeader = "CACHE TYPE"
|
||||||
|
parentHeader = "PARENT"
|
||||||
|
lastUsedSinceHeader = "LAST USED"
|
||||||
|
usageCountHeader = "USAGE"
|
||||||
|
inUseHeader = "IN USE"
|
||||||
|
sharedHeader = "SHARED"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewBuildCacheFormat returns a Format for rendering using a Context
|
||||||
|
func NewBuildCacheFormat(source string, quiet bool) Format {
|
||||||
|
switch source {
|
||||||
|
case TableFormatKey:
|
||||||
|
if quiet {
|
||||||
|
return defaultQuietFormat
|
||||||
|
}
|
||||||
|
return Format(defaultBuildCacheTableFormat)
|
||||||
|
case RawFormatKey:
|
||||||
|
if quiet {
|
||||||
|
return `build_cache_id: {{.ID}}`
|
||||||
|
}
|
||||||
|
format := `build_cache_id: {{.ID}}
|
||||||
|
parent_id: {{.Parent}}
|
||||||
|
build_cache_type: {{.CacheType}}
|
||||||
|
description: {{.Description}}
|
||||||
|
created_at: {{.CreatedAt}}
|
||||||
|
created_since: {{.CreatedSince}}
|
||||||
|
last_used_at: {{.LastUsedAt}}
|
||||||
|
last_used_since: {{.LastUsedSince}}
|
||||||
|
usage_count: {{.UsageCount}}
|
||||||
|
in_use: {{.InUse}}
|
||||||
|
shared: {{.Shared}}
|
||||||
|
`
|
||||||
|
return Format(format)
|
||||||
|
}
|
||||||
|
return Format(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildCacheSort(buildCache []*types.BuildCache) {
|
||||||
|
sort.Slice(buildCache, func(i, j int) bool {
|
||||||
|
lui, luj := buildCache[i].LastUsedAt, buildCache[j].LastUsedAt
|
||||||
|
switch {
|
||||||
|
case lui == nil && luj == nil:
|
||||||
|
return strings.Compare(buildCache[i].ID, buildCache[j].ID) < 0
|
||||||
|
case lui == nil:
|
||||||
|
return true
|
||||||
|
case luj == nil:
|
||||||
|
return false
|
||||||
|
case lui.Equal(*luj):
|
||||||
|
return strings.Compare(buildCache[i].ID, buildCache[j].ID) < 0
|
||||||
|
default:
|
||||||
|
return lui.Before(*luj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildCacheWrite renders the context for a list of containers
|
||||||
|
func BuildCacheWrite(ctx Context, buildCaches []*types.BuildCache) error {
|
||||||
|
render := func(format func(subContext subContext) error) error {
|
||||||
|
buildCacheSort(buildCaches)
|
||||||
|
for _, bc := range buildCaches {
|
||||||
|
err := format(&buildCacheContext{trunc: ctx.Trunc, v: bc})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ctx.Write(newBuildCacheContext(), render)
|
||||||
|
}
|
||||||
|
|
||||||
|
type buildCacheHeaderContext map[string]string
|
||||||
|
|
||||||
|
type buildCacheContext struct {
|
||||||
|
HeaderContext
|
||||||
|
trunc bool
|
||||||
|
v *types.BuildCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuildCacheContext() *buildCacheContext {
|
||||||
|
buildCacheCtx := buildCacheContext{}
|
||||||
|
buildCacheCtx.header = buildCacheHeaderContext{
|
||||||
|
"ID": cacheIDHeader,
|
||||||
|
"Parent": parentHeader,
|
||||||
|
"CacheType": cacheTypeHeader,
|
||||||
|
"Size": sizeHeader,
|
||||||
|
"CreatedSince": createdSinceHeader,
|
||||||
|
"LastUsedSince": lastUsedSinceHeader,
|
||||||
|
"UsageCount": usageCountHeader,
|
||||||
|
"InUse": inUseHeader,
|
||||||
|
"Shared": sharedHeader,
|
||||||
|
"Description": descriptionHeader,
|
||||||
|
}
|
||||||
|
return &buildCacheCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) MarshalJSON() ([]byte, error) {
|
||||||
|
return marshalJSON(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) ID() string {
|
||||||
|
id := c.v.ID
|
||||||
|
if c.trunc {
|
||||||
|
id = stringid.TruncateID(c.v.ID)
|
||||||
|
}
|
||||||
|
if c.v.InUse {
|
||||||
|
return id + "*"
|
||||||
|
}
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) Parent() string {
|
||||||
|
if c.trunc {
|
||||||
|
return stringid.TruncateID(c.v.Parent)
|
||||||
|
}
|
||||||
|
return c.v.Parent
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) CacheType() string {
|
||||||
|
return c.v.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) Description() string {
|
||||||
|
return c.v.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) Size() string {
|
||||||
|
return units.HumanSizeWithPrecision(float64(c.v.Size), 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) CreatedAt() string {
|
||||||
|
return c.v.CreatedAt.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) CreatedSince() string {
|
||||||
|
return units.HumanDuration(time.Now().UTC().Sub(c.v.CreatedAt)) + " ago"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) LastUsedAt() string {
|
||||||
|
if c.v.LastUsedAt == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return c.v.LastUsedAt.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) LastUsedSince() string {
|
||||||
|
if c.v.LastUsedAt == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return units.HumanDuration(time.Now().UTC().Sub(*c.v.LastUsedAt)) + " ago"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) UsageCount() string {
|
||||||
|
return fmt.Sprintf("%d", c.v.UsageCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) InUse() string {
|
||||||
|
return fmt.Sprintf("%t", c.v.InUse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildCacheContext) Shared() string {
|
||||||
|
return fmt.Sprintf("%t", c.v.Shared)
|
||||||
|
}
|
||||||
@@ -12,19 +12,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultDiskUsageImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}} ago\t{{.VirtualSize}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
|
defaultDiskUsageImageTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.ID}}\t{{.CreatedSince}}\t{{.VirtualSize}}\t{{.SharedSize}}\t{{.UniqueSize}}\t{{.Containers}}"
|
||||||
defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}} ago\t{{.Status}}\t{{.Names}}"
|
defaultDiskUsageContainerTableFormat = "table {{.ID}}\t{{.Image}}\t{{.Command}}\t{{.LocalVolumes}}\t{{.Size}}\t{{.RunningFor}}\t{{.Status}}\t{{.Names}}"
|
||||||
defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
|
defaultDiskUsageVolumeTableFormat = "table {{.Name}}\t{{.Links}}\t{{.Size}}"
|
||||||
defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
|
defaultDiskUsageBuildCacheTableFormat = "table {{.ID}}\t{{.CacheType}}\t{{.Size}}\t{{.CreatedSince}}\t{{.LastUsedSince}}\t{{.UsageCount}}\t{{.Shared}}"
|
||||||
defaultBuildCacheVerboseFormat = `
|
defaultDiskUsageTableFormat = "table {{.Type}}\t{{.TotalCount}}\t{{.Active}}\t{{.Size}}\t{{.Reclaimable}}"
|
||||||
ID: {{.ID}}
|
|
||||||
Description: {{.Description}}
|
|
||||||
Mutable: {{.Mutable}}
|
|
||||||
Size: {{.Size}}
|
|
||||||
CreatedAt: {{.CreatedAt}}
|
|
||||||
LastUsedAt: {{.LastUsedAt}}
|
|
||||||
UsageCount: {{.UsageCount}}
|
|
||||||
`
|
|
||||||
|
|
||||||
typeHeader = "TYPE"
|
typeHeader = "TYPE"
|
||||||
totalHeader = "TOTAL"
|
totalHeader = "TOTAL"
|
||||||
@@ -32,7 +24,7 @@ UsageCount: {{.UsageCount}}
|
|||||||
reclaimableHeader = "RECLAIMABLE"
|
reclaimableHeader = "RECLAIMABLE"
|
||||||
containersHeader = "CONTAINERS"
|
containersHeader = "CONTAINERS"
|
||||||
sharedSizeHeader = "SHARED SIZE"
|
sharedSizeHeader = "SHARED SIZE"
|
||||||
uniqueSizeHeader = "UNIQUE SiZE"
|
uniqueSizeHeader = "UNIQUE SIZE"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
|
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
|
||||||
@@ -56,14 +48,26 @@ func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template,
|
|||||||
return ctx.parseFormat()
|
return ctx.parseFormat()
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// NewDiskUsageFormat returns a format for rendering an DiskUsageContext
|
// NewDiskUsageFormat returns a format for rendering an DiskUsageContext
|
||||||
func NewDiskUsageFormat(source string) Format {
|
func NewDiskUsageFormat(source string, verbose bool) Format {
|
||||||
switch source {
|
switch {
|
||||||
case TableFormatKey:
|
case verbose && source == RawFormatKey:
|
||||||
format := defaultDiskUsageTableFormat
|
format := `{{range .Images}}type: Image
|
||||||
return Format(format)
|
` + NewImageFormat(source, false, true) + `
|
||||||
case RawFormatKey:
|
{{end -}}
|
||||||
|
{{range .Containers}}type: Container
|
||||||
|
` + NewContainerFormat(source, false, true) + `
|
||||||
|
{{end -}}
|
||||||
|
{{range .Volumes}}type: Volume
|
||||||
|
` + NewVolumeFormat(source, false) + `
|
||||||
|
{{end -}}
|
||||||
|
{{range .BuildCache}}type: Build Cache
|
||||||
|
` + NewBuildCacheFormat(source, false) + `
|
||||||
|
{{end -}}`
|
||||||
|
return format
|
||||||
|
case !verbose && source == TableFormatKey:
|
||||||
|
return Format(defaultDiskUsageTableFormat)
|
||||||
|
case !verbose && source == RawFormatKey:
|
||||||
format := `type: {{.Type}}
|
format := `type: {{.Type}}
|
||||||
total: {{.TotalCount}}
|
total: {{.TotalCount}}
|
||||||
active: {{.Active}}
|
active: {{.Active}}
|
||||||
@@ -71,8 +75,9 @@ size: {{.Size}}
|
|||||||
reclaimable: {{.Reclaimable}}
|
reclaimable: {{.Reclaimable}}
|
||||||
`
|
`
|
||||||
return Format(format)
|
return Format(format)
|
||||||
|
default:
|
||||||
|
return Format(source)
|
||||||
}
|
}
|
||||||
return Format(source)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *DiskUsageContext) Write() (err error) {
|
func (ctx *DiskUsageContext) Write() (err error) {
|
||||||
@@ -129,14 +134,23 @@ func (ctx *DiskUsageContext) Write() (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *DiskUsageContext) verboseWrite() error {
|
type diskUsageContext struct {
|
||||||
// First images
|
Images []*imageContext
|
||||||
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
|
Containers []*containerContext
|
||||||
if err != nil {
|
Volumes []*volumeContext
|
||||||
return err
|
BuildCache []*buildCacheContext
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Output.Write([]byte("Images space usage:\n\n"))
|
func (ctx *DiskUsageContext) verboseWrite() error {
|
||||||
|
duc := &diskUsageContext{
|
||||||
|
Images: make([]*imageContext, 0, len(ctx.Images)),
|
||||||
|
Containers: make([]*containerContext, 0, len(ctx.Containers)),
|
||||||
|
Volumes: make([]*volumeContext, 0, len(ctx.Volumes)),
|
||||||
|
BuildCache: make([]*buildCacheContext, 0, len(ctx.BuildCache)),
|
||||||
|
}
|
||||||
|
trunc := ctx.Format.IsTable()
|
||||||
|
|
||||||
|
// First images
|
||||||
for _, i := range ctx.Images {
|
for _, i := range ctx.Images {
|
||||||
repo := "<none>"
|
repo := "<none>"
|
||||||
tag := "<none>"
|
tag := "<none>"
|
||||||
@@ -152,55 +166,92 @@ func (ctx *DiskUsageContext) verboseWrite() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ctx.contextFormat(tmpl, &imageContext{
|
duc.Images = append(duc.Images, &imageContext{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
trunc: true,
|
trunc: trunc,
|
||||||
i: *i,
|
i: *i,
|
||||||
})
|
})
|
||||||
if err != nil {
|
}
|
||||||
|
|
||||||
|
// Now containers
|
||||||
|
for _, c := range ctx.Containers {
|
||||||
|
// Don't display the virtual size
|
||||||
|
c.SizeRootFs = 0
|
||||||
|
duc.Containers = append(duc.Containers, &containerContext{trunc: trunc, c: *c})
|
||||||
|
}
|
||||||
|
|
||||||
|
// And volumes
|
||||||
|
for _, v := range ctx.Volumes {
|
||||||
|
duc.Volumes = append(duc.Volumes, &volumeContext{v: *v})
|
||||||
|
}
|
||||||
|
|
||||||
|
// And build cache
|
||||||
|
buildCacheSort(ctx.BuildCache)
|
||||||
|
for _, v := range ctx.BuildCache {
|
||||||
|
duc.BuildCache = append(duc.BuildCache, &buildCacheContext{v: v, trunc: trunc})
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Format == TableFormatKey {
|
||||||
|
return ctx.verboseWriteTable(duc)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.preFormat()
|
||||||
|
tmpl, err := ctx.parseFormat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tmpl.Execute(ctx.Output, duc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *DiskUsageContext) verboseWriteTable(duc *diskUsageContext) error {
|
||||||
|
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ctx.Output.Write([]byte("Images space usage:\n\n"))
|
||||||
|
for _, img := range duc.Images {
|
||||||
|
if err := ctx.contextFormat(tmpl, img); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.postFormat(tmpl, newImageContext())
|
ctx.postFormat(tmpl, newImageContext())
|
||||||
|
|
||||||
// Now containers
|
|
||||||
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
|
|
||||||
tmpl, err = ctx.startSubsection(defaultDiskUsageContainerTableFormat)
|
tmpl, err = ctx.startSubsection(defaultDiskUsageContainerTableFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, c := range ctx.Containers {
|
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
|
||||||
// Don't display the virtual size
|
for _, c := range duc.Containers {
|
||||||
c.SizeRootFs = 0
|
if err := ctx.contextFormat(tmpl, c); err != nil {
|
||||||
err := ctx.contextFormat(tmpl, &containerContext{trunc: true, c: *c})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.postFormat(tmpl, newContainerContext())
|
ctx.postFormat(tmpl, newContainerContext())
|
||||||
|
|
||||||
// And volumes
|
|
||||||
ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
|
|
||||||
tmpl, err = ctx.startSubsection(defaultDiskUsageVolumeTableFormat)
|
tmpl, err = ctx.startSubsection(defaultDiskUsageVolumeTableFormat)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, v := range ctx.Volumes {
|
ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
|
||||||
if err := ctx.contextFormat(tmpl, &volumeContext{v: *v}); err != nil {
|
for _, v := range duc.Volumes {
|
||||||
|
if err := ctx.contextFormat(tmpl, v); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.postFormat(tmpl, newVolumeContext())
|
ctx.postFormat(tmpl, newVolumeContext())
|
||||||
|
|
||||||
// And build cache
|
tmpl, err = ctx.startSubsection(defaultDiskUsageBuildCacheTableFormat)
|
||||||
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
|
if err != nil {
|
||||||
|
return err
|
||||||
t := template.Must(template.New("buildcache").Parse(defaultBuildCacheVerboseFormat))
|
|
||||||
|
|
||||||
for _, v := range ctx.BuildCache {
|
|
||||||
t.Execute(ctx.Output, *v)
|
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
|
||||||
|
for _, v := range duc.BuildCache {
|
||||||
|
if err := ctx.contextFormat(tmpl, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.postFormat(tmpl, newBuildCacheContext())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -416,7 +467,7 @@ func (c *diskUsageBuilderContext) Size() string {
|
|||||||
func (c *diskUsageBuilderContext) Reclaimable() string {
|
func (c *diskUsageBuilderContext) Reclaimable() string {
|
||||||
var inUseBytes int64
|
var inUseBytes int64
|
||||||
for _, bc := range c.buildCache {
|
for _, bc := range c.buildCache {
|
||||||
if bc.InUse {
|
if bc.InUse && !bc.Shared {
|
||||||
inUseBytes += bc.Size
|
inUseBytes += bc.Size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func TestDiskUsageContextFormatWrite(t *testing.T) {
|
|||||||
{
|
{
|
||||||
DiskUsageContext{
|
DiskUsageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: NewDiskUsageFormat("table"),
|
Format: NewDiskUsageFormat("table", false),
|
||||||
},
|
},
|
||||||
Verbose: false},
|
Verbose: false},
|
||||||
`TYPE TOTAL ACTIVE SIZE RECLAIMABLE
|
`TYPE TOTAL ACTIVE SIZE RECLAIMABLE
|
||||||
@@ -29,14 +29,14 @@ Build Cache 0 0 0B
|
|||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
DiskUsageContext{Verbose: true},
|
DiskUsageContext{Verbose: true, Context: Context{Format: NewDiskUsageFormat("table", true)}},
|
||||||
`Images space usage:
|
`Images space usage:
|
||||||
|
|
||||||
REPOSITORY TAG IMAGE ID CREATED ago SIZE SHARED SIZE UNIQUE SiZE CONTAINERS
|
REPOSITORY TAG IMAGE ID CREATED SIZE SHARED SIZE UNIQUE SIZE CONTAINERS
|
||||||
|
|
||||||
Containers space usage:
|
Containers space usage:
|
||||||
|
|
||||||
CONTAINER ID IMAGE COMMAND LOCAL VOLUMES SIZE CREATED ago STATUS NAMES
|
CONTAINER ID IMAGE COMMAND LOCAL VOLUMES SIZE CREATED STATUS NAMES
|
||||||
|
|
||||||
Local Volumes space usage:
|
Local Volumes space usage:
|
||||||
|
|
||||||
@@ -44,8 +44,17 @@ VOLUME NAME LINKS SIZE
|
|||||||
|
|
||||||
Build cache usage: 0B
|
Build cache usage: 0B
|
||||||
|
|
||||||
|
CACHE ID CACHE TYPE SIZE CREATED LAST USED USAGE SHARED
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
DiskUsageContext{Verbose: true, Context: Context{Format: NewDiskUsageFormat("raw", true)}},
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
DiskUsageContext{Verbose: true, Context: Context{Format: NewDiskUsageFormat("{{json .}}", true)}},
|
||||||
|
`{"Images":[],"Containers":[],"Volumes":[],"BuildCache":[]}`,
|
||||||
|
},
|
||||||
// Errors
|
// Errors
|
||||||
{
|
{
|
||||||
DiskUsageContext{
|
DiskUsageContext{
|
||||||
@@ -69,7 +78,7 @@ Build cache usage: 0B
|
|||||||
{
|
{
|
||||||
DiskUsageContext{
|
DiskUsageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: NewDiskUsageFormat("table"),
|
Format: NewDiskUsageFormat("table", false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
`TYPE TOTAL ACTIVE SIZE RECLAIMABLE
|
`TYPE TOTAL ACTIVE SIZE RECLAIMABLE
|
||||||
@@ -82,7 +91,7 @@ Build Cache 0 0 0B
|
|||||||
{
|
{
|
||||||
DiskUsageContext{
|
DiskUsageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: NewDiskUsageFormat("table {{.Type}}\t{{.Active}}"),
|
Format: NewDiskUsageFormat("table {{.Type}}\t{{.Active}}", false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
string(golden.Get(t, "disk-usage-context-write-custom.golden")),
|
string(golden.Get(t, "disk-usage-context-write-custom.golden")),
|
||||||
@@ -91,7 +100,7 @@ Build Cache 0 0 0B
|
|||||||
{
|
{
|
||||||
DiskUsageContext{
|
DiskUsageContext{
|
||||||
Context: Context{
|
Context: Context{
|
||||||
Format: NewDiskUsageFormat("raw"),
|
Format: NewDiskUsageFormat("raw", false),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
string(golden.Get(t, "disk-usage-raw-format.golden")),
|
string(golden.Get(t, "disk-usage-raw-format.golden")),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
clitypes "github.com/docker/cli/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -31,7 +31,7 @@ func NewUpdatesFormat(source string, quiet bool) Format {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdatesWrite writes the context
|
// UpdatesWrite writes the context
|
||||||
func UpdatesWrite(ctx Context, availableUpdates []containerizedengine.Update) error {
|
func UpdatesWrite(ctx Context, availableUpdates []clitypes.Update) error {
|
||||||
render := func(format func(subContext subContext) error) error {
|
render := func(format func(subContext subContext) error) error {
|
||||||
for _, update := range availableUpdates {
|
for _, update := range availableUpdates {
|
||||||
updatesCtx := &updateContext{trunc: ctx.Trunc, u: update}
|
updatesCtx := &updateContext{trunc: ctx.Trunc, u: update}
|
||||||
@@ -53,7 +53,7 @@ func UpdatesWrite(ctx Context, availableUpdates []containerizedengine.Update) er
|
|||||||
type updateContext struct {
|
type updateContext struct {
|
||||||
HeaderContext
|
HeaderContext
|
||||||
trunc bool
|
trunc bool
|
||||||
u containerizedengine.Update
|
u clitypes.Update
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *updateContext) MarshalJSON() ([]byte, error) {
|
func (c *updateContext) MarshalJSON() ([]byte, error) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/cli/internal/containerizedengine"
|
clitypes "github.com/docker/cli/types"
|
||||||
"gotest.tools/assert"
|
"gotest.tools/assert"
|
||||||
is "gotest.tools/assert/cmp"
|
is "gotest.tools/assert/cmp"
|
||||||
)
|
)
|
||||||
@@ -84,7 +84,7 @@ version2
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, testcase := range cases {
|
for _, testcase := range cases {
|
||||||
updates := []containerizedengine.Update{
|
updates := []clitypes.Update{
|
||||||
{Type: "updateType1", Version: "version1", Notes: "description 1"},
|
{Type: "updateType1", Version: "version1", Notes: "description 1"},
|
||||||
{Type: "updateType2", Version: "version2", Notes: "description 2"},
|
{Type: "updateType2", Version: "version2", Notes: "description 2"},
|
||||||
}
|
}
|
||||||
@@ -100,7 +100,7 @@ version2
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateContextWriteJSON(t *testing.T) {
|
func TestUpdateContextWriteJSON(t *testing.T) {
|
||||||
updates := []containerizedengine.Update{
|
updates := []clitypes.Update{
|
||||||
{Type: "updateType1", Version: "version1", Notes: "note1"},
|
{Type: "updateType1", Version: "version1", Notes: "note1"},
|
||||||
{Type: "updateType2", Version: "version2", Notes: "note2"},
|
{Type: "updateType2", Version: "version2", Notes: "note2"},
|
||||||
}
|
}
|
||||||
@@ -124,7 +124,7 @@ func TestUpdateContextWriteJSON(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestUpdateContextWriteJSONField(t *testing.T) {
|
func TestUpdateContextWriteJSONField(t *testing.T) {
|
||||||
updates := []containerizedengine.Update{
|
updates := []clitypes.Update{
|
||||||
{Type: "updateType1", Version: "version1"},
|
{Type: "updateType1", Version: "version1"},
|
||||||
{Type: "updateType2", Version: "version2"},
|
{Type: "updateType2", Version: "version2"},
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-13
@@ -13,7 +13,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
@@ -73,6 +72,7 @@ type buildOptions struct {
|
|||||||
platform string
|
platform string
|
||||||
untrusted bool
|
untrusted bool
|
||||||
secrets []string
|
secrets []string
|
||||||
|
ssh []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// dockerfileFromStdin returns true when the user specified that the Dockerfile
|
// dockerfileFromStdin returns true when the user specified that the Dockerfile
|
||||||
@@ -136,6 +136,8 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
flags.BoolVar(&options.pull, "pull", false, "Always attempt to pull a newer version of the image")
|
flags.BoolVar(&options.pull, "pull", false, "Always attempt to pull a newer version of the image")
|
||||||
flags.StringSliceVar(&options.cacheFrom, "cache-from", []string{}, "Images to consider as cache sources")
|
flags.StringSliceVar(&options.cacheFrom, "cache-from", []string{}, "Images to consider as cache sources")
|
||||||
flags.BoolVar(&options.compress, "compress", false, "Compress the build context using gzip")
|
flags.BoolVar(&options.compress, "compress", false, "Compress the build context using gzip")
|
||||||
|
flags.SetAnnotation("compress", "no-buildkit", nil)
|
||||||
|
|
||||||
flags.StringSliceVar(&options.securityOpt, "security-opt", []string{}, "Security options")
|
flags.StringSliceVar(&options.securityOpt, "security-opt", []string{}, "Security options")
|
||||||
flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
|
flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
|
||||||
flags.SetAnnotation("network", "version", []string{"1.25"})
|
flags.SetAnnotation("network", "version", []string{"1.25"})
|
||||||
@@ -153,11 +155,18 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
flags.BoolVar(&options.stream, "stream", false, "Stream attaches to server to negotiate build context")
|
flags.BoolVar(&options.stream, "stream", false, "Stream attaches to server to negotiate build context")
|
||||||
flags.SetAnnotation("stream", "experimental", nil)
|
flags.SetAnnotation("stream", "experimental", nil)
|
||||||
flags.SetAnnotation("stream", "version", []string{"1.31"})
|
flags.SetAnnotation("stream", "version", []string{"1.31"})
|
||||||
|
flags.SetAnnotation("stream", "no-buildkit", nil)
|
||||||
|
|
||||||
flags.StringVar(&options.progress, "progress", "auto", "Set type of progress output (only if BuildKit enabled) (auto, plain, tty). Use plain to show container output")
|
flags.StringVar(&options.progress, "progress", "auto", "Set type of progress output (auto, plain, tty). Use plain to show container output")
|
||||||
|
flags.SetAnnotation("progress", "buildkit", nil)
|
||||||
|
|
||||||
flags.StringArrayVar(&options.secrets, "secret", []string{}, "Secret file to expose to the build (only if BuildKit enabled): id=mysecret,src=/local/secret")
|
flags.StringArrayVar(&options.secrets, "secret", []string{}, "Secret file to expose to the build (only if BuildKit enabled): id=mysecret,src=/local/secret")
|
||||||
flags.SetAnnotation("secret", "version", []string{"1.39"})
|
flags.SetAnnotation("secret", "version", []string{"1.39"})
|
||||||
|
flags.SetAnnotation("secret", "buildkit", nil)
|
||||||
|
|
||||||
|
flags.StringArrayVar(&options.ssh, "ssh", []string{}, "SSH agent socket or keys to expose to the build (only if BuildKit enabled) (format: default|<id>[=<socket>|<key>[,<key>]])")
|
||||||
|
flags.SetAnnotation("ssh", "version", []string{"1.39"})
|
||||||
|
flags.SetAnnotation("ssh", "buildkit", nil)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,22 +188,17 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
|
|||||||
|
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func runBuild(dockerCli command.Cli, options buildOptions) error {
|
func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||||
if buildkitEnv := os.Getenv("DOCKER_BUILDKIT"); buildkitEnv != "" {
|
buildkitEnabled, err := command.BuildKitEnabled(dockerCli.ServerInfo())
|
||||||
enableBuildkit, err := strconv.ParseBool(buildkitEnv)
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return errors.Wrap(err, "DOCKER_BUILDKIT environment variable expects boolean value")
|
}
|
||||||
}
|
if buildkitEnabled {
|
||||||
if enableBuildkit {
|
|
||||||
return runBuildBuildKit(dockerCli, options)
|
|
||||||
}
|
|
||||||
} else if dockerCli.ServerInfo().BuildkitVersion == types.BuilderBuildKit {
|
|
||||||
return runBuildBuildKit(dockerCli, options)
|
return runBuildBuildKit(dockerCli, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
buildCtx io.ReadCloser
|
buildCtx io.ReadCloser
|
||||||
dockerfileCtx io.ReadCloser
|
dockerfileCtx io.ReadCloser
|
||||||
err error
|
|
||||||
contextDir string
|
contextDir string
|
||||||
tempDir string
|
tempDir string
|
||||||
relDockerfile string
|
relDockerfile string
|
||||||
@@ -346,7 +350,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
|
|||||||
buildCtx = dockerfileCtx
|
buildCtx = dockerfileCtx
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := trySession(dockerCli, contextDir)
|
s, err := trySession(dockerCli, contextDir, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,10 +27,11 @@ import (
|
|||||||
"github.com/moby/buildkit/session/auth/authprovider"
|
"github.com/moby/buildkit/session/auth/authprovider"
|
||||||
"github.com/moby/buildkit/session/filesync"
|
"github.com/moby/buildkit/session/filesync"
|
||||||
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
||||||
|
"github.com/moby/buildkit/session/sshforward/sshprovider"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
"github.com/moby/buildkit/util/progress/progressui"
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/tonistiigi/fsutil"
|
fsutiltypes "github.com/tonistiigi/fsutil/types"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ var errDockerfileConflict = errors.New("ambiguous Dockerfile source: both stdin
|
|||||||
func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
|
func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
s, err := trySession(dockerCli, options.context)
|
s, err := trySession(dockerCli, options.context, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -138,6 +139,13 @@ func runBuildBuildKit(dockerCli command.Cli, options buildOptions) error {
|
|||||||
}
|
}
|
||||||
s.Allow(sp)
|
s.Allow(sp)
|
||||||
}
|
}
|
||||||
|
if len(options.ssh) > 0 {
|
||||||
|
sshp, err := parseSSHSpecs(options.ssh)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "could not parse ssh: %v", options.ssh)
|
||||||
|
}
|
||||||
|
s.Allow(sshp)
|
||||||
|
}
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
@@ -291,7 +299,7 @@ func doBuild(ctx context.Context, eg *errgroup.Group, dockerCli command.Cli, opt
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func resetUIDAndGID(s *fsutil.Stat) bool {
|
func resetUIDAndGID(s *fsutiltypes.Stat) bool {
|
||||||
s.Uid = 0
|
s.Uid = 0
|
||||||
s.Gid = 0
|
s.Gid = 0
|
||||||
return true
|
return true
|
||||||
@@ -408,3 +416,26 @@ func parseSecret(value string) (*secretsprovider.FileSource, error) {
|
|||||||
}
|
}
|
||||||
return &fs, nil
|
return &fs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseSSHSpecs(sl []string) (session.Attachable, error) {
|
||||||
|
configs := make([]sshprovider.AgentConfig, 0, len(sl))
|
||||||
|
for _, v := range sl {
|
||||||
|
c, err := parseSSH(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
configs = append(configs, *c)
|
||||||
|
}
|
||||||
|
return sshprovider.NewSSHAgentProvider(configs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseSSH(value string) (*sshprovider.AgentConfig, error) {
|
||||||
|
parts := strings.SplitN(value, "=", 2)
|
||||||
|
cfg := sshprovider.AgentConfig{
|
||||||
|
ID: parts[0],
|
||||||
|
}
|
||||||
|
if len(parts) > 1 {
|
||||||
|
cfg.Paths = strings.Split(parts[1], ",")
|
||||||
|
}
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,16 +27,16 @@ import (
|
|||||||
|
|
||||||
const clientSessionRemote = "client-session"
|
const clientSessionRemote = "client-session"
|
||||||
|
|
||||||
func isSessionSupported(dockerCli command.Cli) bool {
|
func isSessionSupported(dockerCli command.Cli, forStream bool) bool {
|
||||||
if versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.39") {
|
if !forStream && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.39") {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return dockerCli.ServerInfo().HasExperimental && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31")
|
return dockerCli.ServerInfo().HasExperimental && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31")
|
||||||
}
|
}
|
||||||
|
|
||||||
func trySession(dockerCli command.Cli, contextDir string) (*session.Session, error) {
|
func trySession(dockerCli command.Cli, contextDir string, forStream bool) (*session.Session, error) {
|
||||||
var s *session.Session
|
var s *session.Session
|
||||||
if isSessionSupported(dockerCli) {
|
if isSessionSupported(dockerCli, forStream) {
|
||||||
sharedKey, err := getBuildSharedKey(contextDir)
|
sharedKey, err := getBuildSharedKey(contextDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to get build shared key")
|
return nil, errors.Wrap(err, "failed to get build shared key")
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ type importOptions struct {
|
|||||||
reference string
|
reference string
|
||||||
changes dockeropts.ListOpts
|
changes dockeropts.ListOpts
|
||||||
message string
|
message string
|
||||||
|
platform string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewImportCommand creates a new `docker import` command
|
// NewImportCommand creates a new `docker import` command
|
||||||
@@ -43,6 +44,7 @@ func NewImportCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
options.changes = dockeropts.NewListOpts(nil)
|
options.changes = dockeropts.NewListOpts(nil)
|
||||||
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image")
|
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image")
|
||||||
flags.StringVarP(&options.message, "message", "m", "", "Set commit message for imported image")
|
flags.StringVarP(&options.message, "message", "m", "", "Set commit message for imported image")
|
||||||
|
command.AddPlatformFlag(flags, &options.platform)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -71,8 +73,9 @@ func runImport(dockerCli command.Cli, options importOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
importOptions := types.ImageImportOptions{
|
importOptions := types.ImageImportOptions{
|
||||||
Message: options.message,
|
Message: options.message,
|
||||||
Changes: options.changes.GetAll(),
|
Changes: options.changes.GetAll(),
|
||||||
|
Platform: options.platform,
|
||||||
}
|
}
|
||||||
|
|
||||||
clnt := dockerCli.Client()
|
clnt := dockerCli.Client()
|
||||||
|
|||||||
@@ -3,11 +3,14 @@ package image
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,8 +57,24 @@ Are you sure you want to continue?`
|
|||||||
Are you sure you want to continue?`
|
Are you sure you want to continue?`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// cloneFilter is a temporary workaround that uses existing public APIs from the filters package to clone a filter.
|
||||||
|
// TODO(tiborvass): remove this once filters.Args.Clone() is added.
|
||||||
|
func cloneFilter(args filters.Args) (newArgs filters.Args, err error) {
|
||||||
|
if args.Len() == 0 {
|
||||||
|
return filters.NewArgs(), nil
|
||||||
|
}
|
||||||
|
b, err := args.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return newArgs, err
|
||||||
|
}
|
||||||
|
return filters.FromJSON(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||||
pruneFilters := options.filter.Value()
|
pruneFilters, err := cloneFilter(options.filter.Value())
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", errors.Wrap(err, "could not copy filter in image prune")
|
||||||
|
}
|
||||||
pruneFilters.Add("dangling", fmt.Sprintf("%v", !options.all))
|
pruneFilters.Add("dangling", fmt.Sprintf("%v", !options.all))
|
||||||
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
||||||
|
|
||||||
@@ -73,14 +92,20 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(report.ImagesDeleted) > 0 {
|
if len(report.ImagesDeleted) > 0 {
|
||||||
output = "Deleted Images:\n"
|
var sb strings.Builder
|
||||||
|
sb.WriteString("Deleted Images:\n")
|
||||||
for _, st := range report.ImagesDeleted {
|
for _, st := range report.ImagesDeleted {
|
||||||
if st.Untagged != "" {
|
if st.Untagged != "" {
|
||||||
output += fmt.Sprintln("untagged:", st.Untagged)
|
sb.WriteString("untagged: ")
|
||||||
|
sb.WriteString(st.Untagged)
|
||||||
|
sb.WriteByte('\n')
|
||||||
} else {
|
} else {
|
||||||
output += fmt.Sprintln("deleted:", st.Deleted)
|
sb.WriteString("deleted: ")
|
||||||
|
sb.WriteString(st.Deleted)
|
||||||
|
sb.WriteByte('\n')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
output = sb.String()
|
||||||
spaceReclaimed = report.SpaceReclaimed
|
spaceReclaimed = report.SpaceReclaimed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,14 @@ func TestNewPruneCommandSuccess(t *testing.T) {
|
|||||||
}, nil
|
}, nil
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "label-filter",
|
||||||
|
args: []string{"--force", "--filter", "label=foobar"},
|
||||||
|
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||||
|
assert.Check(t, is.Equal("foobar", pruneFilter.Get("label")[0]))
|
||||||
|
return types.ImagesPruneReport{}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "force-untagged",
|
name: "force-untagged",
|
||||||
args: []string{"--force"},
|
args: []string{"--force"},
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Total reclaimed space: 0B
|
||||||
@@ -18,6 +18,7 @@ type osArch struct {
|
|||||||
// list of valid os/arch values (see "Optional Environment Variables" section
|
// list of valid os/arch values (see "Optional Environment Variables" section
|
||||||
// of https://golang.org/doc/install/source
|
// of https://golang.org/doc/install/source
|
||||||
// Added linux/s390x as we know System z support already exists
|
// Added linux/s390x as we know System z support already exists
|
||||||
|
// Keep in sync with _docker_manifest_annotate in contrib/completion/bash/docker
|
||||||
var validOSArches = map[osArch]bool{
|
var validOSArches = map[osArch]bool{
|
||||||
{os: "darwin", arch: "386"}: true,
|
{os: "darwin", arch: "386"}: true,
|
||||||
{os: "darwin", arch: "amd64"}: true,
|
{os: "darwin", arch: "amd64"}: true,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (output string, err e
|
|||||||
|
|
||||||
// RunPrune calls the Network Prune API
|
// RunPrune calls the Network Prune API
|
||||||
// This returns the amount of space reclaimed and a detailed output string
|
// This returns the amount of space reclaimed and a detailed output string
|
||||||
func RunPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
|
func RunPrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
output, err := runPrune(dockerCli, pruneOptions{force: true, filter: filter})
|
output, err := runPrune(dockerCli, pruneOptions{force: true, filter: filter})
|
||||||
return 0, output, err
|
return 0, output, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
flags.StringVarP(&options.driver, "driver", "d", "", "Secret driver")
|
flags.StringVarP(&options.driver, "driver", "d", "", "Secret driver")
|
||||||
flags.SetAnnotation("driver", "version", []string{"1.31"})
|
flags.SetAnnotation("driver", "version", []string{"1.31"})
|
||||||
flags.StringVar(&options.templateDriver, "template-driver", "", "Template driver")
|
flags.StringVar(&options.templateDriver, "template-driver", "", "Template driver")
|
||||||
flags.SetAnnotation("driver", "version", []string{"1.37"})
|
flags.SetAnnotation("template-driver", "version", []string{"1.37"})
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,6 +302,12 @@ func updateService(ctx context.Context, apiClient client.NetworkAPIClient, flags
|
|||||||
if task.Resources == nil {
|
if task.Resources == nil {
|
||||||
task.Resources = &swarm.ResourceRequirements{}
|
task.Resources = &swarm.ResourceRequirements{}
|
||||||
}
|
}
|
||||||
|
if task.Resources.Limits == nil {
|
||||||
|
task.Resources.Limits = &swarm.Resources{}
|
||||||
|
}
|
||||||
|
if task.Resources.Reservations == nil {
|
||||||
|
task.Resources.Reservations = &swarm.Resources{}
|
||||||
|
}
|
||||||
return task.Resources
|
return task.Resources
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -617,6 +617,38 @@ func TestUpdateIsolationValid(t *testing.T) {
|
|||||||
// and that values are not updated are not reset to their default value
|
// and that values are not updated are not reset to their default value
|
||||||
func TestUpdateLimitsReservations(t *testing.T) {
|
func TestUpdateLimitsReservations(t *testing.T) {
|
||||||
spec := swarm.ServiceSpec{
|
spec := swarm.ServiceSpec{
|
||||||
|
TaskTemplate: swarm.TaskSpec{
|
||||||
|
ContainerSpec: &swarm.ContainerSpec{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that updating works if the service did not previously
|
||||||
|
// have limits set (https://github.com/moby/moby/issues/38363)
|
||||||
|
flags := newUpdateCommand(nil).Flags()
|
||||||
|
err := flags.Set(flagLimitCPU, "2")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
err = flags.Set(flagLimitMemory, "200M")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
err = updateService(context.Background(), nil, flags, &spec)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
spec = swarm.ServiceSpec{
|
||||||
|
TaskTemplate: swarm.TaskSpec{
|
||||||
|
ContainerSpec: &swarm.ContainerSpec{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that updating works if the service did not previously
|
||||||
|
// have reservations set (https://github.com/moby/moby/issues/38363)
|
||||||
|
flags = newUpdateCommand(nil).Flags()
|
||||||
|
err = flags.Set(flagReserveCPU, "2")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
err = flags.Set(flagReserveMemory, "200M")
|
||||||
|
assert.NilError(t, err)
|
||||||
|
err = updateService(context.Background(), nil, flags, &spec)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
spec = swarm.ServiceSpec{
|
||||||
TaskTemplate: swarm.TaskSpec{
|
TaskTemplate: swarm.TaskSpec{
|
||||||
ContainerSpec: &swarm.ContainerSpec{},
|
ContainerSpec: &swarm.ContainerSpec{},
|
||||||
Resources: &swarm.ResourceRequirements{
|
Resources: &swarm.ResourceRequirements{
|
||||||
@@ -632,8 +664,8 @@ func TestUpdateLimitsReservations(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := newUpdateCommand(nil).Flags()
|
flags = newUpdateCommand(nil).Flags()
|
||||||
err := flags.Set(flagLimitCPU, "2")
|
err = flags.Set(flagLimitCPU, "2")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
err = flags.Set(flagReserveCPU, "2")
|
err = flags.Set(flagReserveCPU, "2")
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
apiv1 "k8s.io/api/core/v1"
|
apiv1 "k8s.io/api/core/v1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
runtimeutil "k8s.io/apimachinery/pkg/util/runtime"
|
runtimeutil "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
@@ -240,12 +241,12 @@ func newStackInformer(stacksClient stackListWatch, stackName string) cache.Share
|
|||||||
return cache.NewSharedInformer(
|
return cache.NewSharedInformer(
|
||||||
&cache.ListWatch{
|
&cache.ListWatch{
|
||||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||||
options.LabelSelector = labels.SelectorForStack(stackName)
|
options.FieldSelector = fields.OneTermEqualSelector("metadata.name", stackName).String()
|
||||||
return stacksClient.List(options)
|
return stacksClient.List(options)
|
||||||
},
|
},
|
||||||
|
|
||||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||||
options.LabelSelector = labels.SelectorForStack(stackName)
|
options.FieldSelector = fields.OneTermEqualSelector("metadata.name", stackName).String()
|
||||||
return stacksClient.Watch(options)
|
return stacksClient.Watch(options)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -71,14 +71,14 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
// Image not changed
|
// Image not changed
|
||||||
{
|
{
|
||||||
image: "foobar:1.2.3",
|
image: "foobar:1.2.3",
|
||||||
expectedQueryRegistry: false,
|
expectedQueryRegistry: false,
|
||||||
expectedImage: "foobar:1.2.3@sha256:deadbeef",
|
expectedImage: "foobar:1.2.3@sha256:deadbeef",
|
||||||
expectedForceUpdate: 123,
|
expectedForceUpdate: 123,
|
||||||
},
|
},
|
||||||
// Image changed
|
// Image changed
|
||||||
{
|
{
|
||||||
image: "foobar:1.2.4",
|
image: "foobar:1.2.4",
|
||||||
expectedQueryRegistry: true,
|
expectedQueryRegistry: true,
|
||||||
expectedImage: "foobar:1.2.4",
|
expectedImage: "foobar:1.2.4",
|
||||||
expectedForceUpdate: 123,
|
expectedForceUpdate: 123,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package system
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
@@ -38,10 +37,6 @@ func newDiskUsageCommand(dockerCli command.Cli) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error {
|
func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error {
|
||||||
if opts.verbose && len(opts.format) != 0 {
|
|
||||||
return errors.New("the verbose and the format options conflict")
|
|
||||||
}
|
|
||||||
|
|
||||||
du, err := dockerCli.Client().DiskUsage(context.Background())
|
du, err := dockerCli.Client().DiskUsage(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -52,13 +47,20 @@ func runDiskUsage(dockerCli command.Cli, opts diskUsageOptions) error {
|
|||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var bsz int64
|
||||||
|
for _, bc := range du.BuildCache {
|
||||||
|
if !bc.Shared {
|
||||||
|
bsz += bc.Size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
duCtx := formatter.DiskUsageContext{
|
duCtx := formatter.DiskUsageContext{
|
||||||
Context: formatter.Context{
|
Context: formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewDiskUsageFormat(format),
|
Format: formatter.NewDiskUsageFormat(format, opts.verbose),
|
||||||
},
|
},
|
||||||
LayersSize: du.LayersSize,
|
LayersSize: du.LayersSize,
|
||||||
BuilderSize: du.BuilderSize,
|
BuilderSize: bsz,
|
||||||
BuildCache: du.BuildCache,
|
BuildCache: du.BuildCache,
|
||||||
Images: du.Images,
|
Images: du.Images,
|
||||||
Containers: du.Containers,
|
Containers: du.Containers,
|
||||||
|
|||||||
@@ -34,12 +34,20 @@ func runDialStdio(dockerCli command.Cli) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to open the raw stream connection")
|
return errors.Wrap(err, "failed to open the raw stream connection")
|
||||||
}
|
}
|
||||||
connHalfCloser, ok := conn.(halfCloser)
|
defer conn.Close()
|
||||||
if !ok {
|
|
||||||
|
var connHalfCloser halfCloser
|
||||||
|
switch t := conn.(type) {
|
||||||
|
case halfCloser:
|
||||||
|
connHalfCloser = t
|
||||||
|
case halfReadWriteCloser:
|
||||||
|
connHalfCloser = &nopCloseReader{t}
|
||||||
|
default:
|
||||||
return errors.New("the raw stream connection does not implement halfCloser")
|
return errors.New("the raw stream connection does not implement halfCloser")
|
||||||
}
|
}
|
||||||
stdin2conn := make(chan error)
|
|
||||||
conn2stdout := make(chan error)
|
stdin2conn := make(chan error, 1)
|
||||||
|
conn2stdout := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
stdin2conn <- copier(connHalfCloser, &halfReadCloserWrapper{os.Stdin}, "stdin to stream")
|
stdin2conn <- copier(connHalfCloser, &halfReadCloserWrapper{os.Stdin}, "stdin to stream")
|
||||||
}()
|
}()
|
||||||
@@ -90,6 +98,19 @@ type halfCloser interface {
|
|||||||
halfWriteCloser
|
halfWriteCloser
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type halfReadWriteCloser interface {
|
||||||
|
io.Reader
|
||||||
|
halfWriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
type nopCloseReader struct {
|
||||||
|
halfReadWriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *nopCloseReader) CloseRead() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type halfReadCloserWrapper struct {
|
type halfReadCloserWrapper struct {
|
||||||
io.ReadCloser
|
io.ReadCloser
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,6 +204,9 @@ func prettyPrintInfo(dockerCli command.Cli, info types.Info) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(dockerCli.Out(), "Live Restore Enabled:", info.LiveRestoreEnabled)
|
fmt.Fprintln(dockerCli.Out(), "Live Restore Enabled:", info.LiveRestoreEnabled)
|
||||||
|
if info.ProductLicense != "" {
|
||||||
|
fmt.Fprintln(dockerCli.Out(), "Product License:", info.ProductLicense)
|
||||||
|
}
|
||||||
fmt.Fprint(dockerCli.Out(), "\n")
|
fmt.Fprint(dockerCli.Out(), "\n")
|
||||||
|
|
||||||
printWarnings(dockerCli, info)
|
printWarnings(dockerCli, info)
|
||||||
|
|||||||
+13
-23
@@ -2,12 +2,12 @@ package system
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/command/builder"
|
||||||
"github.com/docker/cli/cli/command/container"
|
"github.com/docker/cli/cli/command/container"
|
||||||
"github.com/docker/cli/cli/command/image"
|
"github.com/docker/cli/cli/command/image"
|
||||||
"github.com/docker/cli/cli/command/network"
|
"github.com/docker/cli/cli/command/network"
|
||||||
@@ -21,20 +21,21 @@ import (
|
|||||||
type pruneOptions struct {
|
type pruneOptions struct {
|
||||||
force bool
|
force bool
|
||||||
all bool
|
all bool
|
||||||
pruneBuildCache bool
|
|
||||||
pruneVolumes bool
|
pruneVolumes bool
|
||||||
|
pruneBuildCache bool
|
||||||
filter opts.FilterOpt
|
filter opts.FilterOpt
|
||||||
}
|
}
|
||||||
|
|
||||||
// newPruneCommand creates a new cobra.Command for `docker prune`
|
// newPruneCommand creates a new cobra.Command for `docker prune`
|
||||||
func newPruneCommand(dockerCli command.Cli) *cobra.Command {
|
func newPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
options := pruneOptions{filter: opts.NewFilterOpt(), pruneBuildCache: true}
|
options := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune [OPTIONS]",
|
Use: "prune [OPTIONS]",
|
||||||
Short: "Remove unused data",
|
Short: "Remove unused data",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
options.pruneBuildCache = versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31")
|
||||||
return runPrune(dockerCli, options)
|
return runPrune(dockerCli, options)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{"version": "1.25"},
|
Annotations: map[string]string{"version": "1.25"},
|
||||||
@@ -57,44 +58,29 @@ const confirmationTemplate = `WARNING! This will remove:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
Are you sure you want to continue?`
|
Are you sure you want to continue?`
|
||||||
|
|
||||||
// runBuildCachePrune executes a prune command for build cache
|
|
||||||
func runBuildCachePrune(dockerCli command.Cli, _ opts.FilterOpt) (uint64, string, error) {
|
|
||||||
report, err := dockerCli.Client().BuildCachePrune(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
return report.SpaceReclaimed, "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func runPrune(dockerCli command.Cli, options pruneOptions) error {
|
func runPrune(dockerCli command.Cli, options pruneOptions) error {
|
||||||
// TODO version this once "until" filter is supported for volumes
|
// TODO version this once "until" filter is supported for volumes
|
||||||
if options.pruneVolumes && options.filter.Value().Contains("until") {
|
if options.pruneVolumes && options.filter.Value().Contains("until") {
|
||||||
return fmt.Errorf(`ERROR: The "until" filter is not supported with "--volumes"`)
|
return fmt.Errorf(`ERROR: The "until" filter is not supported with "--volumes"`)
|
||||||
}
|
}
|
||||||
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.31") {
|
|
||||||
options.pruneBuildCache = false
|
|
||||||
}
|
|
||||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), confirmationMessage(options)) {
|
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), confirmationMessage(options)) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
imagePrune := func(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
|
pruneFuncs := []func(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error){
|
||||||
return image.RunPrune(dockerCli, options.all, options.filter)
|
|
||||||
}
|
|
||||||
pruneFuncs := []func(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error){
|
|
||||||
container.RunPrune,
|
container.RunPrune,
|
||||||
network.RunPrune,
|
network.RunPrune,
|
||||||
}
|
}
|
||||||
if options.pruneVolumes {
|
if options.pruneVolumes {
|
||||||
pruneFuncs = append(pruneFuncs, volume.RunPrune)
|
pruneFuncs = append(pruneFuncs, volume.RunPrune)
|
||||||
}
|
}
|
||||||
pruneFuncs = append(pruneFuncs, imagePrune)
|
pruneFuncs = append(pruneFuncs, image.RunPrune)
|
||||||
if options.pruneBuildCache {
|
if options.pruneBuildCache {
|
||||||
pruneFuncs = append(pruneFuncs, runBuildCachePrune)
|
pruneFuncs = append(pruneFuncs, builder.CachePrune)
|
||||||
}
|
}
|
||||||
|
|
||||||
var spaceReclaimed uint64
|
var spaceReclaimed uint64
|
||||||
for _, pruneFn := range pruneFuncs {
|
for _, pruneFn := range pruneFuncs {
|
||||||
spc, output, err := pruneFn(dockerCli, options.filter)
|
spc, output, err := pruneFn(dockerCli, options.all, options.filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -126,7 +112,11 @@ func confirmationMessage(options pruneOptions) string {
|
|||||||
warnings = append(warnings, "all dangling images")
|
warnings = append(warnings, "all dangling images")
|
||||||
}
|
}
|
||||||
if options.pruneBuildCache {
|
if options.pruneBuildCache {
|
||||||
warnings = append(warnings, "all build cache")
|
if options.all {
|
||||||
|
warnings = append(warnings, "all build cache")
|
||||||
|
} else {
|
||||||
|
warnings = append(warnings, "all dangling build cache")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(options.filter.String()) > 0 {
|
if len(options.filter.String()) > 0 {
|
||||||
warnings = append(warnings, "Elements to be pruned will be filtered with:")
|
warnings = append(warnings, "Elements to be pruned will be filtered with:")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
@@ -69,12 +70,14 @@ func loadPrivKey(streams command.Streams, keyPath string, options keyLoadOptions
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getPrivKeyBytesFromPath(keyPath string) ([]byte, error) {
|
func getPrivKeyBytesFromPath(keyPath string) ([]byte, error) {
|
||||||
fileInfo, err := os.Stat(keyPath)
|
if runtime.GOOS != "windows" {
|
||||||
if err != nil {
|
fileInfo, err := os.Stat(keyPath)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
if fileInfo.Mode()&nonOwnerReadWriteMask != 0 {
|
}
|
||||||
return nil, fmt.Errorf("private key file %s must not be readable or writable by others", keyPath)
|
if fileInfo.Mode()&nonOwnerReadWriteMask != 0 {
|
||||||
|
return nil, fmt.Errorf("private key file %s must not be readable or writable by others", keyPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
from, err := os.OpenFile(keyPath, os.O_RDONLY, notary.PrivExecPerms)
|
from, err := os.OpenFile(keyPath, os.O_RDONLY, notary.PrivExecPerms)
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ func TestAddStageSigners(t *testing.T) {
|
|||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
changeList := cl.List()
|
changeList := cl.List()
|
||||||
assert.Check(t, is.Len(changeList, 4))
|
assert.Check(t, is.Len(changeList, 4))
|
||||||
// ordering is determinstic:
|
// ordering is deterministic:
|
||||||
|
|
||||||
// first change is for targets/user key creation
|
// first change is for targets/user key creation
|
||||||
newSignerKeyChange := changeList[0]
|
newSignerKeyChange := changeList[0]
|
||||||
|
|||||||
@@ -73,6 +73,6 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
|
|||||||
|
|
||||||
// RunPrune calls the Volume Prune API
|
// RunPrune calls the Volume Prune API
|
||||||
// This returns the amount of space reclaimed and a detailed output string
|
// This returns the amount of space reclaimed and a detailed output string
|
||||||
func RunPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) {
|
func RunPrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) {
|
||||||
return runPrune(dockerCli, pruneOptions{force: true, filter: filter})
|
return runPrune(dockerCli, pruneOptions{force: true, filter: filter})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ var interpolateTypeCastMapping = map[interp.Path]interp.Cast{
|
|||||||
servicePath("deploy", "replicas"): toInt,
|
servicePath("deploy", "replicas"): toInt,
|
||||||
servicePath("deploy", "update_config", "parallelism"): toInt,
|
servicePath("deploy", "update_config", "parallelism"): toInt,
|
||||||
servicePath("deploy", "update_config", "max_failure_ratio"): toFloat,
|
servicePath("deploy", "update_config", "max_failure_ratio"): toFloat,
|
||||||
|
servicePath("deploy", "rollback_config", "parallelism"): toInt,
|
||||||
|
servicePath("deploy", "rollback_config", "max_failure_ratio"): toFloat,
|
||||||
servicePath("deploy", "restart_policy", "max_attempts"): toInt,
|
servicePath("deploy", "restart_policy", "max_attempts"): toInt,
|
||||||
servicePath("ports", interp.PathMatchList, "target"): toInt,
|
servicePath("ports", interp.PathMatchList, "target"): toInt,
|
||||||
servicePath("ports", interp.PathMatchList, "published"): toInt,
|
servicePath("ports", interp.PathMatchList, "published"): toInt,
|
||||||
|
|||||||
@@ -476,12 +476,13 @@ func resolveVolumePaths(volumes []types.ServiceVolumeConfig, workingDir string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
filePath := expandUser(volume.Source, lookupEnv)
|
filePath := expandUser(volume.Source, lookupEnv)
|
||||||
// Check for a Unix absolute path first, to handle a Windows client
|
// Check if source is an absolute path (either Unix or Windows), to
|
||||||
// with a Unix daemon. This handles a Windows client connecting to a
|
// handle a Windows client with a Unix daemon or vice-versa.
|
||||||
// Unix daemon. Note that this is not required for Docker for Windows
|
//
|
||||||
// when specifying a local Windows path, because Docker for Windows
|
// Note that this is not required for Docker for Windows when specifying
|
||||||
// translates the Windows path into a valid path within the VM.
|
// a local Windows path, because Docker for Windows translates the Windows
|
||||||
if !path.IsAbs(filePath) {
|
// path into a valid path within the VM.
|
||||||
|
if !path.IsAbs(filePath) && !isAbs(filePath) {
|
||||||
filePath = absPath(workingDir, filePath)
|
filePath = absPath(workingDir, filePath)
|
||||||
}
|
}
|
||||||
volume.Source = filePath
|
volume.Source = filePath
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ volumes:
|
|||||||
|
|
||||||
func TestLoadWithInterpolationCastFull(t *testing.T) {
|
func TestLoadWithInterpolationCastFull(t *testing.T) {
|
||||||
dict, err := ParseYAML([]byte(`
|
dict, err := ParseYAML([]byte(`
|
||||||
version: "3.4"
|
version: "3.7"
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
configs:
|
configs:
|
||||||
@@ -524,6 +524,9 @@ services:
|
|||||||
update_config:
|
update_config:
|
||||||
parallelism: $theint
|
parallelism: $theint
|
||||||
max_failure_ratio: $thefloat
|
max_failure_ratio: $thefloat
|
||||||
|
rollback_config:
|
||||||
|
parallelism: $theint
|
||||||
|
max_failure_ratio: $thefloat
|
||||||
restart_policy:
|
restart_policy:
|
||||||
max_attempts: $theint
|
max_attempts: $theint
|
||||||
ports:
|
ports:
|
||||||
@@ -574,7 +577,7 @@ networks:
|
|||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
expected := &types.Config{
|
expected := &types.Config{
|
||||||
Filename: "filename.yml",
|
Filename: "filename.yml",
|
||||||
Version: "3.4",
|
Version: "3.7",
|
||||||
Services: []types.ServiceConfig{
|
Services: []types.ServiceConfig{
|
||||||
{
|
{
|
||||||
Name: "web",
|
Name: "web",
|
||||||
@@ -600,6 +603,10 @@ networks:
|
|||||||
Parallelism: uint64Ptr(555),
|
Parallelism: uint64Ptr(555),
|
||||||
MaxFailureRatio: 3.14,
|
MaxFailureRatio: 3.14,
|
||||||
},
|
},
|
||||||
|
RollbackConfig: &types.UpdateConfig{
|
||||||
|
Parallelism: uint64Ptr(555),
|
||||||
|
MaxFailureRatio: 3.14,
|
||||||
|
},
|
||||||
RestartPolicy: &types.RestartPolicy{
|
RestartPolicy: &types.RestartPolicy{
|
||||||
MaxAttempts: uint64Ptr(555),
|
MaxAttempts: uint64Ptr(555),
|
||||||
},
|
},
|
||||||
@@ -902,6 +909,84 @@ services:
|
|||||||
assert.Error(t, err, `invalid mount config for type "bind": field Source must not be empty`)
|
assert.Error(t, err, `invalid mount config for type "bind": field Source must not be empty`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLoadBindMountSourceIsWindowsAbsolute(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
doc string
|
||||||
|
yaml string
|
||||||
|
expected types.ServiceVolumeConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
doc: "Z-drive lowercase",
|
||||||
|
yaml: `
|
||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
windows:
|
||||||
|
image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: z:\
|
||||||
|
target: c:\data
|
||||||
|
`,
|
||||||
|
expected: types.ServiceVolumeConfig{Type: "bind", Source: `z:\`, Target: `c:\data`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Z-drive uppercase",
|
||||||
|
yaml: `
|
||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
windows:
|
||||||
|
image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: Z:\
|
||||||
|
target: C:\data
|
||||||
|
`,
|
||||||
|
expected: types.ServiceVolumeConfig{Type: "bind", Source: `Z:\`, Target: `C:\data`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "Z-drive subdirectory",
|
||||||
|
yaml: `
|
||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
windows:
|
||||||
|
image: mcr.microsoft.com/windows/servercore/iis:windowsservercore-ltsc2019
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: Z:\some-dir
|
||||||
|
target: C:\data
|
||||||
|
`,
|
||||||
|
expected: types.ServiceVolumeConfig{Type: "bind", Source: `Z:\some-dir`, Target: `C:\data`},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
doc: "forward-slashes",
|
||||||
|
yaml: `
|
||||||
|
version: '3.3'
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
image: app:latest
|
||||||
|
volumes:
|
||||||
|
- type: bind
|
||||||
|
source: /z/some-dir
|
||||||
|
target: /c/data
|
||||||
|
`,
|
||||||
|
expected: types.ServiceVolumeConfig{Type: "bind", Source: `/z/some-dir`, Target: `/c/data`},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.doc, func(t *testing.T) {
|
||||||
|
config, err := loadYAML(tc.yaml)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Check(t, is.Len(config.Services[0].Volumes, 1))
|
||||||
|
assert.Check(t, is.DeepEqual(tc.expected, config.Services[0].Volumes[0]))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestLoadBindMountWithSource(t *testing.T) {
|
func TestLoadBindMountWithSource(t *testing.T) {
|
||||||
config, err := loadYAML(`
|
config, err := loadYAML(`
|
||||||
version: "3.5"
|
version: "3.5"
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package loader
|
||||||
|
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
// https://github.com/golang/go/blob/master/LICENSE
|
||||||
|
|
||||||
|
// This file contains utilities to check for Windows absolute paths on Linux.
|
||||||
|
// The code in this file was largely copied from the Golang filepath package
|
||||||
|
// https://github.com/golang/go/blob/1d0e94b1e13d5e8a323a63cd1cc1ef95290c9c36/src/path/filepath/path_windows.go#L12-L65
|
||||||
|
|
||||||
|
func isSlash(c uint8) bool {
|
||||||
|
return c == '\\' || c == '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAbs reports whether the path is a Windows absolute path.
|
||||||
|
func isAbs(path string) (b bool) {
|
||||||
|
l := volumeNameLen(path)
|
||||||
|
if l == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
path = path[l:]
|
||||||
|
if path == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return isSlash(path[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// volumeNameLen returns length of the leading volume name on Windows.
|
||||||
|
// It returns 0 elsewhere.
|
||||||
|
// nolint: gocyclo
|
||||||
|
func volumeNameLen(path string) int {
|
||||||
|
if len(path) < 2 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
// with drive letter
|
||||||
|
c := path[0]
|
||||||
|
if path[1] == ':' && ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z') {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
// is it UNC? https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx
|
||||||
|
if l := len(path); l >= 5 && isSlash(path[0]) && isSlash(path[1]) &&
|
||||||
|
!isSlash(path[2]) && path[2] != '.' {
|
||||||
|
// first, leading `\\` and next shouldn't be `\`. its server name.
|
||||||
|
for n := 3; n < l-1; n++ {
|
||||||
|
// second, next '\' shouldn't be repeated.
|
||||||
|
if isSlash(path[n]) {
|
||||||
|
n++
|
||||||
|
// third, following something characters. its share name.
|
||||||
|
if !isSlash(path[n]) {
|
||||||
|
if path[n] == '.' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for ; n < l; n++ {
|
||||||
|
if isSlash(path[n]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package loader
|
||||||
|
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
// https://github.com/golang/go/blob/master/LICENSE
|
||||||
|
|
||||||
|
// The code in this file was copied from the Golang filepath package with some
|
||||||
|
// small modifications to run it on non-Windows platforms.
|
||||||
|
// https://github.com/golang/go/blob/1d0e94b1e13d5e8a323a63cd1cc1ef95290c9c36/src/path/filepath/path_test.go#L711-L763
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
type IsAbsTest struct {
|
||||||
|
path string
|
||||||
|
isAbs bool
|
||||||
|
}
|
||||||
|
|
||||||
|
var isabstests = []IsAbsTest{
|
||||||
|
{"", false},
|
||||||
|
{"/", true},
|
||||||
|
{"/usr/bin/gcc", true},
|
||||||
|
{"..", false},
|
||||||
|
{"/a/../bb", true},
|
||||||
|
{".", false},
|
||||||
|
{"./", false},
|
||||||
|
{"lala", false},
|
||||||
|
}
|
||||||
|
|
||||||
|
var winisabstests = []IsAbsTest{
|
||||||
|
{`C:\`, true},
|
||||||
|
{`c\`, false},
|
||||||
|
{`c::`, false},
|
||||||
|
{`c:`, false},
|
||||||
|
{`/`, false},
|
||||||
|
{`\`, false},
|
||||||
|
{`\Windows`, false},
|
||||||
|
{`c:a\b`, false},
|
||||||
|
{`c:\a\b`, true},
|
||||||
|
{`c:/a/b`, true},
|
||||||
|
{`\\host\share\foo`, true},
|
||||||
|
{`//host/share/foo/bar`, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsAbs(t *testing.T) {
|
||||||
|
tests := append(isabstests, winisabstests...)
|
||||||
|
// All non-windows tests should fail, because they have no volume letter.
|
||||||
|
for _, test := range isabstests {
|
||||||
|
tests = append(tests, IsAbsTest{test.path, false})
|
||||||
|
}
|
||||||
|
// All non-windows test should work as intended if prefixed with volume letter.
|
||||||
|
for _, test := range isabstests {
|
||||||
|
tests = append(tests, IsAbsTest{"c:" + test.path, test.isAbs})
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range winisabstests {
|
||||||
|
if r := isAbs(test.path); r != test.isAbs {
|
||||||
|
t.Errorf("IsAbs(%q) = %v, want %v", test.path, r, test.isAbs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -176,15 +176,21 @@ func extractVariable(value interface{}, pattern *regexp.Regexp) ([]extractedValu
|
|||||||
|
|
||||||
// Soft default (fall back if unset or empty)
|
// Soft default (fall back if unset or empty)
|
||||||
func softDefault(substitution string, mapping Mapping) (string, bool, error) {
|
func softDefault(substitution string, mapping Mapping) (string, bool, error) {
|
||||||
return withDefault(substitution, mapping, "-:")
|
sep := ":-"
|
||||||
|
if !strings.Contains(substitution, sep) {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
name, defaultValue := partition(substitution, sep)
|
||||||
|
value, ok := mapping(name)
|
||||||
|
if !ok || value == "" {
|
||||||
|
return defaultValue, true, nil
|
||||||
|
}
|
||||||
|
return value, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hard default (fall back if-and-only-if empty)
|
// Hard default (fall back if-and-only-if empty)
|
||||||
func hardDefault(substitution string, mapping Mapping) (string, bool, error) {
|
func hardDefault(substitution string, mapping Mapping) (string, bool, error) {
|
||||||
return withDefault(substitution, mapping, "-")
|
sep := "-"
|
||||||
}
|
|
||||||
|
|
||||||
func withDefault(substitution string, mapping Mapping, sep string) (string, bool, error) {
|
|
||||||
if !strings.Contains(substitution, sep) {
|
if !strings.Contains(substitution, sep) {
|
||||||
return "", false, nil
|
return "", false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,12 @@ func TestEmptyValueWithSoftDefault(t *testing.T) {
|
|||||||
assert.Check(t, is.Equal("ok def", result))
|
assert.Check(t, is.Equal("ok def", result))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValueWithSoftDefault(t *testing.T) {
|
||||||
|
result, err := Substitute("ok ${FOO:-def}", defaultMapping)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Check(t, is.Equal("ok first", result))
|
||||||
|
}
|
||||||
|
|
||||||
func TestEmptyValueWithHardDefault(t *testing.T) {
|
func TestEmptyValueWithHardDefault(t *testing.T) {
|
||||||
result, err := Substitute("ok ${BAR-def}", defaultMapping)
|
result, err := Substitute("ok ${BAR-def}", defaultMapping)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|||||||
+12
-19
@@ -150,9 +150,8 @@ func TestOldValidAuth(t *testing.T) {
|
|||||||
|
|
||||||
// defaultIndexserver is https://index.docker.io/v1/
|
// defaultIndexserver is https://index.docker.io/v1/
|
||||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
if ac.Username != "joejoe" || ac.Password != "hello" {
|
assert.Equal(t, ac.Username, "joejoe")
|
||||||
t.Fatalf("Missing data from parsing:\n%q", config)
|
assert.Equal(t, ac.Password, "hello")
|
||||||
}
|
|
||||||
|
|
||||||
// Now save it and make sure it shows up in new form
|
// Now save it and make sure it shows up in new form
|
||||||
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
@@ -213,9 +212,8 @@ func TestOldJSON(t *testing.T) {
|
|||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
if ac.Username != "joejoe" || ac.Password != "hello" {
|
assert.Equal(t, ac.Username, "joejoe")
|
||||||
t.Fatalf("Missing data from parsing:\n%q", config)
|
assert.Equal(t, ac.Password, "hello")
|
||||||
}
|
|
||||||
|
|
||||||
// Now save it and make sure it shows up in new form
|
// Now save it and make sure it shows up in new form
|
||||||
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
@@ -249,9 +247,8 @@ func TestNewJSON(t *testing.T) {
|
|||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
if ac.Username != "joejoe" || ac.Password != "hello" {
|
assert.Equal(t, ac.Username, "joejoe")
|
||||||
t.Fatalf("Missing data from parsing:\n%q", config)
|
assert.Equal(t, ac.Password, "hello")
|
||||||
}
|
|
||||||
|
|
||||||
// Now save it and make sure it shows up in new form
|
// Now save it and make sure it shows up in new form
|
||||||
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
@@ -284,9 +281,8 @@ func TestNewJSONNoEmail(t *testing.T) {
|
|||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
if ac.Username != "joejoe" || ac.Password != "hello" {
|
assert.Equal(t, ac.Username, "joejoe")
|
||||||
t.Fatalf("Missing data from parsing:\n%q", config)
|
assert.Equal(t, ac.Password, "hello")
|
||||||
}
|
|
||||||
|
|
||||||
// Now save it and make sure it shows up in new form
|
// Now save it and make sure it shows up in new form
|
||||||
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
configStr := saveConfigAndValidateNewFormat(t, config, tmpHome)
|
||||||
@@ -431,10 +427,8 @@ func TestJSONReaderNoFile(t *testing.T) {
|
|||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
if ac.Username != "joejoe" || ac.Password != "hello" {
|
assert.Equal(t, ac.Username, "joejoe")
|
||||||
t.Fatalf("Missing data from parsing:\n%q", config)
|
assert.Equal(t, ac.Password, "hello")
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOldJSONReaderNoFile(t *testing.T) {
|
func TestOldJSONReaderNoFile(t *testing.T) {
|
||||||
@@ -444,9 +438,8 @@ func TestOldJSONReaderNoFile(t *testing.T) {
|
|||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
|
|
||||||
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
ac := config.AuthConfigs["https://index.docker.io/v1/"]
|
||||||
if ac.Username != "joejoe" || ac.Password != "hello" {
|
assert.Equal(t, ac.Username, "joejoe")
|
||||||
t.Fatalf("Missing data from parsing:\n%q", config)
|
assert.Equal(t, ac.Password, "hello")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONWithPsFormatNoFile(t *testing.T) {
|
func TestJSONWithPsFormatNoFile(t *testing.T) {
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/connhelper/ssh"
|
"github.com/docker/cli/cli/connhelper/ssh"
|
||||||
@@ -82,6 +84,9 @@ func newCommandConn(ctx context.Context, cmd string, args ...string) (net.Conn,
|
|||||||
// commandConn implements net.Conn
|
// commandConn implements net.Conn
|
||||||
type commandConn struct {
|
type commandConn struct {
|
||||||
cmd *exec.Cmd
|
cmd *exec.Cmd
|
||||||
|
cmdExited bool
|
||||||
|
cmdWaitErr error
|
||||||
|
cmdMutex sync.Mutex
|
||||||
stdin io.WriteCloser
|
stdin io.WriteCloser
|
||||||
stdout io.ReadCloser
|
stdout io.ReadCloser
|
||||||
stderrMu sync.Mutex
|
stderrMu sync.Mutex
|
||||||
@@ -101,23 +106,75 @@ func (c *commandConn) killIfStdioClosed() error {
|
|||||||
if !stdioClosed {
|
if !stdioClosed {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
var err error
|
return c.kill()
|
||||||
// NOTE: maybe already killed here
|
}
|
||||||
if err = c.cmd.Process.Kill(); err == nil {
|
|
||||||
err = c.cmd.Wait()
|
// killAndWait tries sending SIGTERM to the process before sending SIGKILL.
|
||||||
|
func killAndWait(cmd *exec.Cmd) error {
|
||||||
|
var werr error
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
werrCh := make(chan error)
|
||||||
|
go func() { werrCh <- cmd.Wait() }()
|
||||||
|
cmd.Process.Signal(syscall.SIGTERM)
|
||||||
|
select {
|
||||||
|
case werr = <-werrCh:
|
||||||
|
case <-time.After(3 * time.Second):
|
||||||
|
cmd.Process.Kill()
|
||||||
|
werr = <-werrCh
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmd.Process.Kill()
|
||||||
|
werr = cmd.Wait()
|
||||||
}
|
}
|
||||||
if err != nil {
|
return werr
|
||||||
// err is typically "os: process already finished".
|
}
|
||||||
// we check ProcessState here instead of `strings.Contains(err, "os: process already finished")`
|
|
||||||
if c.cmd.ProcessState.Exited() {
|
// kill returns nil if the command terminated, regardless to the exit status.
|
||||||
err = nil
|
func (c *commandConn) kill() error {
|
||||||
|
var werr error
|
||||||
|
c.cmdMutex.Lock()
|
||||||
|
if c.cmdExited {
|
||||||
|
werr = c.cmdWaitErr
|
||||||
|
} else {
|
||||||
|
werr = killAndWait(c.cmd)
|
||||||
|
c.cmdWaitErr = werr
|
||||||
|
c.cmdExited = true
|
||||||
|
}
|
||||||
|
c.cmdMutex.Unlock()
|
||||||
|
if werr == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
wExitErr, ok := werr.(*exec.ExitError)
|
||||||
|
if ok {
|
||||||
|
if wExitErr.ProcessState.Exited() {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return errors.Wrapf(werr, "connhelper: failed to wait")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *commandConn) onEOF(eof error) error {
|
func (c *commandConn) onEOF(eof error) error {
|
||||||
werr := c.cmd.Wait()
|
// when we got EOF, the command is going to be terminated
|
||||||
|
var werr error
|
||||||
|
c.cmdMutex.Lock()
|
||||||
|
if c.cmdExited {
|
||||||
|
werr = c.cmdWaitErr
|
||||||
|
} else {
|
||||||
|
werrCh := make(chan error)
|
||||||
|
go func() { werrCh <- c.cmd.Wait() }()
|
||||||
|
select {
|
||||||
|
case werr = <-werrCh:
|
||||||
|
c.cmdWaitErr = werr
|
||||||
|
c.cmdExited = true
|
||||||
|
case <-time.After(10 * time.Second):
|
||||||
|
c.cmdMutex.Unlock()
|
||||||
|
c.stderrMu.Lock()
|
||||||
|
stderr := c.stderr.String()
|
||||||
|
c.stderrMu.Unlock()
|
||||||
|
return errors.Errorf("command %v did not exit after %v: stderr=%q", c.cmd.Args, eof, stderr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c.cmdMutex.Unlock()
|
||||||
if werr == nil {
|
if werr == nil {
|
||||||
return eof
|
return eof
|
||||||
}
|
}
|
||||||
@@ -148,7 +205,10 @@ func (c *commandConn) CloseRead() error {
|
|||||||
c.stdioClosedMu.Lock()
|
c.stdioClosedMu.Lock()
|
||||||
c.stdoutClosed = true
|
c.stdoutClosed = true
|
||||||
c.stdioClosedMu.Unlock()
|
c.stdioClosedMu.Unlock()
|
||||||
return c.killIfStdioClosed()
|
if err := c.killIfStdioClosed(); err != nil {
|
||||||
|
logrus.Warnf("commandConn.CloseRead: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *commandConn) Read(p []byte) (int, error) {
|
func (c *commandConn) Read(p []byte) (int, error) {
|
||||||
@@ -167,7 +227,10 @@ func (c *commandConn) CloseWrite() error {
|
|||||||
c.stdioClosedMu.Lock()
|
c.stdioClosedMu.Lock()
|
||||||
c.stdinClosed = true
|
c.stdinClosed = true
|
||||||
c.stdioClosedMu.Unlock()
|
c.stdioClosedMu.Unlock()
|
||||||
return c.killIfStdioClosed()
|
if err := c.killIfStdioClosed(); err != nil {
|
||||||
|
logrus.Warnf("commandConn.CloseWrite: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *commandConn) Write(p []byte) (int, error) {
|
func (c *commandConn) Write(p []byte) (int, error) {
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ func continueOnError(err error) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, each func(context.Context, distribution.Repository, reference.Named) (bool, error)) error {
|
func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named, each func(context.Context, distribution.Repository, reference.Named) (bool, error)) error {
|
||||||
endpoints, err := allEndpoints(namedRef)
|
endpoints, err := allEndpoints(namedRef, c.insecureRegistry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named,
|
|||||||
repoEndpoint := repositoryEndpoint{endpoint: endpoint, info: repoInfo}
|
repoEndpoint := repositoryEndpoint{endpoint: endpoint, info: repoInfo}
|
||||||
repo, err := c.getRepositoryForReference(ctx, namedRef, repoEndpoint)
|
repo, err := c.getRepositoryForReference(ctx, namedRef, repoEndpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Debugf("error with repo endpoint %s: %s", repoEndpoint, err)
|
logrus.Debugf("error %s with repo endpoint %+v", err, repoEndpoint)
|
||||||
if _, ok := err.(ErrHTTPProto); ok {
|
if _, ok := err.(ErrHTTPProto); ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -262,12 +262,18 @@ func (c *client) iterateEndpoints(ctx context.Context, namedRef reference.Named,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// allEndpoints returns a list of endpoints ordered by priority (v2, https, v1).
|
// allEndpoints returns a list of endpoints ordered by priority (v2, https, v1).
|
||||||
func allEndpoints(namedRef reference.Named) ([]registry.APIEndpoint, error) {
|
func allEndpoints(namedRef reference.Named, insecure bool) ([]registry.APIEndpoint, error) {
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(namedRef)
|
repoInfo, err := registry.ParseRepositoryInfo(namedRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
registryService, err := registry.NewService(registry.ServiceOptions{})
|
|
||||||
|
var serviceOpts registry.ServiceOptions
|
||||||
|
if insecure {
|
||||||
|
logrus.Debugf("allowing insecure registry for: %s", reference.Domain(namedRef))
|
||||||
|
serviceOpts.InsecureRegistries = []string{reference.Domain(namedRef)}
|
||||||
|
}
|
||||||
|
registryService, err := registry.NewService(serviceOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []registry.APIEndpoint{}, err
|
return []registry.APIEndpoint{}, err
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-4
@@ -13,6 +13,7 @@ import (
|
|||||||
cliconfig "github.com/docker/cli/cli/config"
|
cliconfig "github.com/docker/cli/cli/config"
|
||||||
"github.com/docker/cli/cli/debug"
|
"github.com/docker/cli/cli/debug"
|
||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
|
"github.com/docker/cli/internal/containerizedengine"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
@@ -32,6 +33,9 @@ func newDockerCommand(dockerCli *command.DockerCli) *cobra.Command {
|
|||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
TraverseChildren: true,
|
TraverseChildren: true,
|
||||||
Args: noArgs,
|
Args: noArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return command.ShowHelp(dockerCli.Err())(cmd, args)
|
||||||
|
},
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// flags must be the top-level command flags, not cmd.Flags()
|
// flags must be the top-level command flags, not cmd.Flags()
|
||||||
opts.Common.SetDefaultOptions(flags)
|
opts.Common.SetDefaultOptions(flags)
|
||||||
@@ -99,8 +103,10 @@ func setHelpFunc(dockerCli *command.DockerCli, cmd *cobra.Command, flags *pflag.
|
|||||||
ccmd.Println(err)
|
ccmd.Println(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := hideUnsupportedFeatures(ccmd, dockerCli); err != nil {
|
||||||
hideUnsupportedFeatures(ccmd, dockerCli)
|
ccmd.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
defaultHelpFunc(ccmd, args)
|
defaultHelpFunc(ccmd, args)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -168,7 +174,7 @@ func main() {
|
|||||||
stdin, stdout, stderr := term.StdStreams()
|
stdin, stdout, stderr := term.StdStreams()
|
||||||
logrus.SetOutput(stderr)
|
logrus.SetOutput(stderr)
|
||||||
|
|
||||||
dockerCli := command.NewDockerCli(stdin, stdout, stderr, contentTrustEnabled())
|
dockerCli := command.NewDockerCli(stdin, stdout, stderr, contentTrustEnabled(), containerizedengine.NewClient)
|
||||||
cmd := newDockerCommand(dockerCli)
|
cmd := newDockerCommand(dockerCli)
|
||||||
|
|
||||||
if err := cmd.Execute(); err != nil {
|
if err := cmd.Execute(); err != nil {
|
||||||
@@ -234,15 +240,21 @@ func hideFeatureSubCommand(subcmd *cobra.Command, hasFeature bool, annotation st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) error {
|
||||||
clientVersion := details.Client().ClientVersion()
|
clientVersion := details.Client().ClientVersion()
|
||||||
osType := details.ServerInfo().OSType
|
osType := details.ServerInfo().OSType
|
||||||
hasExperimental := details.ServerInfo().HasExperimental
|
hasExperimental := details.ServerInfo().HasExperimental
|
||||||
hasExperimentalCLI := details.ClientInfo().HasExperimental
|
hasExperimentalCLI := details.ClientInfo().HasExperimental
|
||||||
|
hasBuildKit, err := command.BuildKitEnabled(details.ServerInfo())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||||
hideFeatureFlag(f, hasExperimental, "experimental")
|
hideFeatureFlag(f, hasExperimental, "experimental")
|
||||||
hideFeatureFlag(f, hasExperimentalCLI, "experimentalCLI")
|
hideFeatureFlag(f, hasExperimentalCLI, "experimentalCLI")
|
||||||
|
hideFeatureFlag(f, hasBuildKit, "buildkit")
|
||||||
|
hideFeatureFlag(f, !hasBuildKit, "no-buildkit")
|
||||||
// hide flags not supported by the server
|
// hide flags not supported by the server
|
||||||
if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
|
if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
|
||||||
f.Hidden = true
|
f.Hidden = true
|
||||||
@@ -258,6 +270,8 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
|||||||
for _, subcmd := range cmd.Commands() {
|
for _, subcmd := range cmd.Commands() {
|
||||||
hideFeatureSubCommand(subcmd, hasExperimental, "experimental")
|
hideFeatureSubCommand(subcmd, hasExperimental, "experimental")
|
||||||
hideFeatureSubCommand(subcmd, hasExperimentalCLI, "experimentalCLI")
|
hideFeatureSubCommand(subcmd, hasExperimentalCLI, "experimentalCLI")
|
||||||
|
hideFeatureSubCommand(subcmd, hasBuildKit, "buildkit")
|
||||||
|
hideFeatureSubCommand(subcmd, !hasBuildKit, "no-buildkit")
|
||||||
// hide subcommands not supported by the server
|
// hide subcommands not supported by the server
|
||||||
if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
|
if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
|
||||||
subcmd.Hidden = true
|
subcmd.Hidden = true
|
||||||
@@ -266,6 +280,7 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
|||||||
subcmd.Hidden = true
|
subcmd.Hidden = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a command or one of its ancestors is in the list
|
// Checks if a command or one of its ancestors is in the list
|
||||||
@@ -312,6 +327,7 @@ func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
|
|||||||
if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
|
if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
|
||||||
errs = append(errs, fmt.Sprintf("\"--%s\" is on a Docker cli with experimental cli features enabled", f.Name))
|
errs = append(errs, fmt.Sprintf("\"--%s\" is on a Docker cli with experimental cli features enabled", f.Name))
|
||||||
}
|
}
|
||||||
|
// buildkit-specific flags are noop when buildkit is not enabled, so we do not add an error in that case
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -26,8 +27,25 @@ func TestClientDebugEnabled(t *testing.T) {
|
|||||||
|
|
||||||
func TestExitStatusForInvalidSubcommandWithHelpFlag(t *testing.T) {
|
func TestExitStatusForInvalidSubcommandWithHelpFlag(t *testing.T) {
|
||||||
discard := ioutil.Discard
|
discard := ioutil.Discard
|
||||||
cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard, false))
|
cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard, false, nil))
|
||||||
cmd.SetArgs([]string{"help", "invalid"})
|
cmd.SetArgs([]string{"help", "invalid"})
|
||||||
err := cmd.Execute()
|
err := cmd.Execute()
|
||||||
assert.Error(t, err, "unknown help topic: invalid")
|
assert.Error(t, err, "unknown help topic: invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExitStatusForInvalidSubcommand(t *testing.T) {
|
||||||
|
discard := ioutil.Discard
|
||||||
|
cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard, false, nil))
|
||||||
|
cmd.SetArgs([]string{"invalid"})
|
||||||
|
err := cmd.Execute()
|
||||||
|
assert.Check(t, is.ErrorContains(err, "docker: 'invalid' is not a docker command."))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVersion(t *testing.T) {
|
||||||
|
var b bytes.Buffer
|
||||||
|
cmd := newDockerCommand(command.NewDockerCli(os.Stdin, &b, &b, false, nil))
|
||||||
|
cmd.SetArgs([]string{"--version"})
|
||||||
|
err := cmd.Execute()
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.Check(t, is.Contains(b.String(), "Docker version"))
|
||||||
|
}
|
||||||
|
|||||||
+181
-39
@@ -1,12 +1,14 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# shellcheck disable=SC2016,SC2119,SC2155
|
# shellcheck disable=SC2016,SC2119,SC2155,SC2206,SC2207
|
||||||
#
|
#
|
||||||
# Shellcheck ignore list:
|
# Shellcheck ignore list:
|
||||||
# - SC2016: Expressions don't expand in single quotes, use double quotes for that.
|
# - SC2016: Expressions don't expand in single quotes, use double quotes for that.
|
||||||
# - SC2119: Use foo "$@" if function's $1 should mean script's $1.
|
# - SC2119: Use foo "$@" if function's $1 should mean script's $1.
|
||||||
# - SC2155: Declare and assign separately to avoid masking return values.
|
# - SC2155: Declare and assign separately to avoid masking return values.
|
||||||
#
|
# - SC2206: Quote to prevent word splitting, or split robustly with mapfile or read -a.
|
||||||
# You can find more details for each warning at the following page:
|
# - SC2207: Prefer mapfile or read -a to split command output (or quote to avoid splitting).
|
||||||
|
#
|
||||||
|
# You can find more details for each warning at the following page:
|
||||||
# https://github.com/koalaman/shellcheck/wiki/<SCXXXX>
|
# https://github.com/koalaman/shellcheck/wiki/<SCXXXX>
|
||||||
#
|
#
|
||||||
# bash completion file for core docker commands
|
# bash completion file for core docker commands
|
||||||
@@ -563,23 +565,39 @@ __docker_append_to_completions() {
|
|||||||
COMPREPLY=( ${COMPREPLY[@]/%/"$1"} )
|
COMPREPLY=( ${COMPREPLY[@]/%/"$1"} )
|
||||||
}
|
}
|
||||||
|
|
||||||
# __docker_daemon_is_experimental tests whether the currently configured Docker
|
# __docker_fetch_info fetches information about the configured Docker server and updates
|
||||||
# daemon runs in experimental mode. If so, the function exits with 0 (true).
|
# several variables with the results.
|
||||||
# Otherwise, or if the result cannot be determined, the exit value is 1 (false).
|
# The result is cached for the duration of one invocation of bash completion.
|
||||||
__docker_daemon_is_experimental() {
|
__docker_fetch_info() {
|
||||||
[ "$(__docker_q version -f '{{.Server.Experimental}}')" = "true" ]
|
if [ -z "$info_fetched" ] ; then
|
||||||
|
read -r client_experimental server_experimental server_os <<< "$(__docker_q version -f '{{.Client.Experimental}} {{.Server.Experimental}} {{.Server.Os}}')"
|
||||||
|
info_fetched=true
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# __docker_daemon_os_is tests whether the currently configured Docker daemon runs
|
# __docker_client_is_experimental tests whether the Docker cli is configured to support
|
||||||
|
# experimental features. If so, the function exits with 0 (true).
|
||||||
|
# Otherwise, or if the result cannot be determined, the exit value is 1 (false).
|
||||||
|
__docker_client_is_experimental() {
|
||||||
|
__docker_fetch_info
|
||||||
|
[ "$client_experimental" = "true" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# __docker_server_is_experimental tests whether the currently configured Docker
|
||||||
|
# server runs in experimental mode. If so, the function exits with 0 (true).
|
||||||
|
# Otherwise, or if the result cannot be determined, the exit value is 1 (false).
|
||||||
|
__docker_server_is_experimental() {
|
||||||
|
__docker_fetch_info
|
||||||
|
[ "$server_experimental" = "true" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
# __docker_server_os_is tests whether the currently configured Docker server runs
|
||||||
# on the operating system passed in as the first argument.
|
# on the operating system passed in as the first argument.
|
||||||
# It does so by querying the daemon for its OS. The result is cached for the duration
|
|
||||||
# of one invocation of bash completion so that this function can be used to test for
|
|
||||||
# several different operating systems without additional costs.
|
|
||||||
# Known operating systems: linux, windows.
|
# Known operating systems: linux, windows.
|
||||||
__docker_daemon_os_is() {
|
__docker_server_os_is() {
|
||||||
local expected_os="$1"
|
local expected_os="$1"
|
||||||
local actual_os=${daemon_os=$(__docker_q version -f '{{.Server.Os}}')}
|
__docker_fetch_info
|
||||||
[ "$actual_os" = "$expected_os" ]
|
[ "$server_os" = "$expected_os" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
# __docker_stack_orchestrator_is tests whether the client is configured to use
|
# __docker_stack_orchestrator_is tests whether the client is configured to use
|
||||||
@@ -865,6 +883,7 @@ __docker_complete_log_drivers() {
|
|||||||
gelf
|
gelf
|
||||||
journald
|
journald
|
||||||
json-file
|
json-file
|
||||||
|
local
|
||||||
logentries
|
logentries
|
||||||
none
|
none
|
||||||
splunk
|
splunk
|
||||||
@@ -888,7 +907,8 @@ __docker_complete_log_options() {
|
|||||||
local gcplogs_options="$common_options1 $common_options2 gcp-log-cmd gcp-meta-id gcp-meta-name gcp-meta-zone gcp-project"
|
local gcplogs_options="$common_options1 $common_options2 gcp-log-cmd gcp-meta-id gcp-meta-name gcp-meta-zone gcp-project"
|
||||||
local gelf_options="$common_options1 $common_options2 gelf-address gelf-compression-level gelf-compression-type gelf-tcp-max-reconnect gelf-tcp-reconnect-delay tag"
|
local gelf_options="$common_options1 $common_options2 gelf-address gelf-compression-level gelf-compression-type gelf-tcp-max-reconnect gelf-tcp-reconnect-delay tag"
|
||||||
local journald_options="$common_options1 $common_options2 tag"
|
local journald_options="$common_options1 $common_options2 tag"
|
||||||
local json_file_options="$common_options1 $common_options2 max-file max-size"
|
local json_file_options="$common_options1 $common_options2 compress max-file max-size"
|
||||||
|
local local_options="$common_options1 compress max-file max-size"
|
||||||
local logentries_options="$common_options1 $common_options2 line-only logentries-token tag"
|
local logentries_options="$common_options1 $common_options2 line-only logentries-token tag"
|
||||||
local splunk_options="$common_options1 $common_options2 splunk-caname splunk-capath splunk-format splunk-gzip splunk-gzip-level splunk-index splunk-insecureskipverify splunk-source splunk-sourcetype splunk-token splunk-url splunk-verify-connection tag"
|
local splunk_options="$common_options1 $common_options2 splunk-caname splunk-capath splunk-format splunk-gzip splunk-gzip-level splunk-index splunk-insecureskipverify splunk-source splunk-sourcetype splunk-token splunk-url splunk-verify-connection tag"
|
||||||
local syslog_options="$common_options1 $common_options2 syslog-address syslog-facility syslog-format syslog-tls-ca-cert syslog-tls-cert syslog-tls-key syslog-tls-skip-verify tag"
|
local syslog_options="$common_options1 $common_options2 syslog-address syslog-facility syslog-format syslog-tls-ca-cert syslog-tls-cert syslog-tls-key syslog-tls-skip-verify tag"
|
||||||
@@ -917,6 +937,9 @@ __docker_complete_log_options() {
|
|||||||
json-file)
|
json-file)
|
||||||
COMPREPLY=( $( compgen -W "$json_file_options" -S = -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "$json_file_options" -S = -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
|
local)
|
||||||
|
COMPREPLY=( $( compgen -W "$local_options" -S = -- "$cur" ) )
|
||||||
|
;;
|
||||||
logentries)
|
logentries)
|
||||||
COMPREPLY=( $( compgen -W "$logentries_options" -S = -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "$logentries_options" -S = -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
@@ -946,7 +969,7 @@ __docker_complete_log_driver_options() {
|
|||||||
__docker_nospace
|
__docker_nospace
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
fluentd-async-connect)
|
compress|fluentd-async-connect)
|
||||||
COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) )
|
COMPREPLY=( $( compgen -W "false true" -- "${cur##*=}" ) )
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
@@ -1128,7 +1151,8 @@ _docker_docker() {
|
|||||||
*)
|
*)
|
||||||
local counter=$( __docker_pos_first_nonflag "$(__docker_to_extglob "$global_options_with_args")" )
|
local counter=$( __docker_pos_first_nonflag "$(__docker_to_extglob "$global_options_with_args")" )
|
||||||
if [ "$cword" -eq "$counter" ]; then
|
if [ "$cword" -eq "$counter" ]; then
|
||||||
__docker_daemon_is_experimental && commands+=(${experimental_commands[*]})
|
__docker_client_is_experimental && commands+=(${experimental_client_commands[*]})
|
||||||
|
__docker_server_is_experimental && commands+=(${experimental_server_commands[*]})
|
||||||
COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) )
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
@@ -1837,14 +1861,14 @@ _docker_container_run_and_create() {
|
|||||||
--volume -v
|
--volume -v
|
||||||
--workdir -w
|
--workdir -w
|
||||||
"
|
"
|
||||||
__docker_daemon_os_is windows && options_with_args+="
|
__docker_server_os_is windows && options_with_args+="
|
||||||
--cpu-count
|
--cpu-count
|
||||||
--cpu-percent
|
--cpu-percent
|
||||||
--io-maxbandwidth
|
--io-maxbandwidth
|
||||||
--io-maxiops
|
--io-maxiops
|
||||||
--isolation
|
--isolation
|
||||||
"
|
"
|
||||||
__docker_daemon_is_experimental && options_with_args+="
|
__docker_server_is_experimental && options_with_args+="
|
||||||
--platform
|
--platform
|
||||||
"
|
"
|
||||||
|
|
||||||
@@ -1960,7 +1984,7 @@ _docker_container_run_and_create() {
|
|||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--isolation)
|
--isolation)
|
||||||
if __docker_daemon_os_is windows ; then
|
if __docker_server_os_is windows ; then
|
||||||
__docker_complete_isolation
|
__docker_complete_isolation
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@@ -2071,12 +2095,12 @@ _docker_container_start() {
|
|||||||
__docker_complete_detach_keys && return
|
__docker_complete_detach_keys && return
|
||||||
case "$prev" in
|
case "$prev" in
|
||||||
--checkpoint)
|
--checkpoint)
|
||||||
if __docker_daemon_is_experimental ; then
|
if __docker_server_is_experimental ; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
--checkpoint-dir)
|
--checkpoint-dir)
|
||||||
if __docker_daemon_is_experimental ; then
|
if __docker_server_is_experimental ; then
|
||||||
_filedir -d
|
_filedir -d
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@@ -2086,7 +2110,7 @@ _docker_container_start() {
|
|||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
local options="--attach -a --detach-keys --help --interactive -i"
|
local options="--attach -a --detach-keys --help --interactive -i"
|
||||||
__docker_daemon_is_experimental && options+=" --checkpoint --checkpoint-dir"
|
__docker_server_is_experimental && options+=" --checkpoint --checkpoint-dir"
|
||||||
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
@@ -2449,7 +2473,7 @@ _docker_daemon() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_docker_deploy() {
|
_docker_deploy() {
|
||||||
__docker_daemon_is_experimental && _docker_stack_deploy
|
__docker_server_is_experimental && _docker_stack_deploy
|
||||||
}
|
}
|
||||||
|
|
||||||
_docker_diff() {
|
_docker_diff() {
|
||||||
@@ -2535,7 +2559,7 @@ _docker_image_build() {
|
|||||||
--target
|
--target
|
||||||
--ulimit
|
--ulimit
|
||||||
"
|
"
|
||||||
__docker_daemon_os_is windows && options_with_args+="
|
__docker_server_os_is windows && options_with_args+="
|
||||||
--isolation
|
--isolation
|
||||||
"
|
"
|
||||||
|
|
||||||
@@ -2549,7 +2573,7 @@ _docker_image_build() {
|
|||||||
--quiet -q
|
--quiet -q
|
||||||
--rm
|
--rm
|
||||||
"
|
"
|
||||||
if __docker_daemon_is_experimental ; then
|
if __docker_server_is_experimental ; then
|
||||||
options_with_args+="
|
options_with_args+="
|
||||||
--platform
|
--platform
|
||||||
"
|
"
|
||||||
@@ -2584,7 +2608,7 @@ _docker_image_build() {
|
|||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
--isolation)
|
--isolation)
|
||||||
if __docker_daemon_os_is windows ; then
|
if __docker_server_os_is windows ; then
|
||||||
__docker_complete_isolation
|
__docker_complete_isolation
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
@@ -2664,14 +2688,16 @@ _docker_image_images() {
|
|||||||
|
|
||||||
_docker_image_import() {
|
_docker_image_import() {
|
||||||
case "$prev" in
|
case "$prev" in
|
||||||
--change|-c|--message|-m)
|
--change|-c|--message|-m|--platform)
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
COMPREPLY=( $( compgen -W "--change -c --help --message -m" -- "$cur" ) )
|
local options="--change -c --help --message -m"
|
||||||
|
__docker_server_is_experimental && options+=" --platform"
|
||||||
|
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
local counter=$(__docker_pos_first_nonflag '--change|-c|--message|-m')
|
local counter=$(__docker_pos_first_nonflag '--change|-c|--message|-m')
|
||||||
@@ -2779,7 +2805,7 @@ _docker_image_pull() {
|
|||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
local options="--all-tags -a --disable-content-trust=false --help"
|
local options="--all-tags -a --disable-content-trust=false --help"
|
||||||
__docker_daemon_is_experimental && options+=" --platform"
|
__docker_server_is_experimental && options+=" --platform"
|
||||||
|
|
||||||
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
|
||||||
;;
|
;;
|
||||||
@@ -3395,7 +3421,6 @@ _docker_service_update_and_create() {
|
|||||||
local options_with_args="
|
local options_with_args="
|
||||||
--endpoint-mode
|
--endpoint-mode
|
||||||
--entrypoint
|
--entrypoint
|
||||||
--force
|
|
||||||
--health-cmd
|
--health-cmd
|
||||||
--health-interval
|
--health-interval
|
||||||
--health-retries
|
--health-retries
|
||||||
@@ -3431,7 +3456,7 @@ _docker_service_update_and_create() {
|
|||||||
--user -u
|
--user -u
|
||||||
--workdir -w
|
--workdir -w
|
||||||
"
|
"
|
||||||
__docker_daemon_os_is windows && options_with_args+="
|
__docker_server_os_is windows && options_with_args+="
|
||||||
--credential-spec
|
--credential-spec
|
||||||
"
|
"
|
||||||
|
|
||||||
@@ -3520,6 +3545,10 @@ _docker_service_update_and_create() {
|
|||||||
--secret-rm
|
--secret-rm
|
||||||
"
|
"
|
||||||
|
|
||||||
|
boolean_options="$boolean_options
|
||||||
|
--force
|
||||||
|
"
|
||||||
|
|
||||||
case "$prev" in
|
case "$prev" in
|
||||||
--env-rm)
|
--env-rm)
|
||||||
COMPREPLY=( $( compgen -e -- "$cur" ) )
|
COMPREPLY=( $( compgen -e -- "$cur" ) )
|
||||||
@@ -3817,6 +3846,109 @@ _docker_swarm_update() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_docker_manifest() {
|
||||||
|
local subcommands="
|
||||||
|
annotate
|
||||||
|
create
|
||||||
|
inspect
|
||||||
|
push
|
||||||
|
"
|
||||||
|
__docker_subcommands "$subcommands" && return
|
||||||
|
|
||||||
|
case "$cur" in
|
||||||
|
-*)
|
||||||
|
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) )
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_docker_manifest_annotate() {
|
||||||
|
case "$prev" in
|
||||||
|
--arch)
|
||||||
|
COMPREPLY=( $( compgen -W "
|
||||||
|
386
|
||||||
|
amd64
|
||||||
|
arm
|
||||||
|
arm64
|
||||||
|
mips64
|
||||||
|
mips64le
|
||||||
|
ppc64le
|
||||||
|
s390x" -- "$cur" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--os)
|
||||||
|
COMPREPLY=( $( compgen -W "
|
||||||
|
darwin
|
||||||
|
dragonfly
|
||||||
|
freebsd
|
||||||
|
linux
|
||||||
|
netbsd
|
||||||
|
openbsd
|
||||||
|
plan9
|
||||||
|
solaris
|
||||||
|
windows" -- "$cur" ) )
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
--os-features|--variant)
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$cur" in
|
||||||
|
-*)
|
||||||
|
COMPREPLY=( $( compgen -W "--arch --help --os --os-features --variant" -- "$cur" ) )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local counter=$( __docker_pos_first_nonflag "--arch|--os|--os-features|--variant" )
|
||||||
|
if [ "$cword" -eq "$counter" ] || [ "$cword" -eq "$((counter + 1))" ]; then
|
||||||
|
__docker_complete_images --force-tag --id
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_docker_manifest_create() {
|
||||||
|
case "$cur" in
|
||||||
|
-*)
|
||||||
|
COMPREPLY=( $( compgen -W "--amend -a --help --insecure" -- "$cur" ) )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
__docker_complete_images --force-tag --id
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_docker_manifest_inspect() {
|
||||||
|
case "$cur" in
|
||||||
|
-*)
|
||||||
|
COMPREPLY=( $( compgen -W "--help --insecure --verbose -v" -- "$cur" ) )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local counter=$( __docker_pos_first_nonflag )
|
||||||
|
if [ "$cword" -eq "$counter" ] || [ "$cword" -eq "$((counter + 1))" ]; then
|
||||||
|
__docker_complete_images --force-tag --id
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
_docker_manifest_push() {
|
||||||
|
case "$cur" in
|
||||||
|
-*)
|
||||||
|
COMPREPLY=( $( compgen -W "--help --insecure --purge -p" -- "$cur" ) )
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
local counter=$( __docker_pos_first_nonflag )
|
||||||
|
if [ "$cword" -eq "$counter" ]; then
|
||||||
|
__docker_complete_images --force-tag --id
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
_docker_node() {
|
_docker_node() {
|
||||||
local subcommands="
|
local subcommands="
|
||||||
demote
|
demote
|
||||||
@@ -4451,7 +4583,7 @@ _docker_stack_deploy() {
|
|||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
local options="--compose-file -c --help --orchestrator"
|
local options="--compose-file -c --help --orchestrator"
|
||||||
__docker_daemon_is_experimental && __docker_stack_orchestrator_is swarm && options+=" --bundle-file"
|
__docker_server_is_experimental && __docker_stack_orchestrator_is swarm && options+=" --bundle-file"
|
||||||
__docker_stack_orchestrator_is kubernetes && options+=" --kubeconfig --namespace"
|
__docker_stack_orchestrator_is kubernetes && options+=" --kubeconfig --namespace"
|
||||||
__docker_stack_orchestrator_is swarm && options+=" --prune --resolve-image --with-registry-auth"
|
__docker_stack_orchestrator_is swarm && options+=" --prune --resolve-image --with-registry-auth"
|
||||||
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
|
COMPREPLY=( $( compgen -W "$options" -- "$cur" ) )
|
||||||
@@ -4716,6 +4848,10 @@ _docker_system_events() {
|
|||||||
__docker_complete_networks --cur "${cur##*=}"
|
__docker_complete_networks --cur "${cur##*=}"
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
|
node)
|
||||||
|
__docker_complete_nodes --cur "${cur##*=}"
|
||||||
|
return
|
||||||
|
;;
|
||||||
scope)
|
scope)
|
||||||
COMPREPLY=( $( compgen -W "local swarm" -- "${cur##*=}" ) )
|
COMPREPLY=( $( compgen -W "local swarm" -- "${cur##*=}" ) )
|
||||||
return
|
return
|
||||||
@@ -4732,7 +4868,7 @@ _docker_system_events() {
|
|||||||
|
|
||||||
case "$prev" in
|
case "$prev" in
|
||||||
--filter|-f)
|
--filter|-f)
|
||||||
COMPREPLY=( $( compgen -S = -W "container daemon event image label network scope type volume" -- "$cur" ) )
|
COMPREPLY=( $( compgen -S = -W "container daemon event image label network node scope type volume" -- "$cur" ) )
|
||||||
__docker_nospace
|
__docker_nospace
|
||||||
return
|
return
|
||||||
;;
|
;;
|
||||||
@@ -5074,7 +5210,11 @@ _docker() {
|
|||||||
wait
|
wait
|
||||||
)
|
)
|
||||||
|
|
||||||
local experimental_commands=(
|
local experimental_client_commands=(
|
||||||
|
manifest
|
||||||
|
)
|
||||||
|
|
||||||
|
local experimental_server_commands=(
|
||||||
checkpoint
|
checkpoint
|
||||||
deploy
|
deploy
|
||||||
)
|
)
|
||||||
@@ -5098,10 +5238,12 @@ _docker() {
|
|||||||
--tlskey
|
--tlskey
|
||||||
"
|
"
|
||||||
|
|
||||||
local host config daemon_os
|
# variables to cache server info, populated on demand for performance reasons
|
||||||
|
local info_fetched server_experimental server_os
|
||||||
# variables to cache client info, populated on demand for performance reasons
|
# variables to cache client info, populated on demand for performance reasons
|
||||||
local stack_orchestrator_is_kubernetes stack_orchestrator_is_swarm
|
local client_experimental stack_orchestrator_is_kubernetes stack_orchestrator_is_swarm
|
||||||
|
|
||||||
|
local host config
|
||||||
|
|
||||||
COMPREPLY=()
|
COMPREPLY=()
|
||||||
local cur prev words cword
|
local cur prev words cword
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
# - Felix Riedel
|
# - Felix Riedel
|
||||||
# - Steve Durrheimer
|
# - Steve Durrheimer
|
||||||
# - Vincent Bernat
|
# - Vincent Bernat
|
||||||
|
# - Rohan Verma
|
||||||
#
|
#
|
||||||
# license:
|
# license:
|
||||||
#
|
#
|
||||||
@@ -2781,7 +2782,7 @@ __docker_subcommand() {
|
|||||||
$opts_help \
|
$opts_help \
|
||||||
"($help -p --password)"{-p=,--password=}"[Password]:password: " \
|
"($help -p --password)"{-p=,--password=}"[Password]:password: " \
|
||||||
"($help)--password-stdin[Read password from stdin]" \
|
"($help)--password-stdin[Read password from stdin]" \
|
||||||
"($help -u --user)"{-u=,--user=}"[Username]:username: " \
|
"($help -u --username)"{-u=,--username=}"[Username]:username: " \
|
||||||
"($help -)1:server: " && ret=0
|
"($help -)1:server: " && ret=0
|
||||||
;;
|
;;
|
||||||
(logout)
|
(logout)
|
||||||
|
|||||||
+25
-14
@@ -10,35 +10,45 @@ LINTER_IMAGE_NAME = docker-cli-lint$(IMAGE_TAG)
|
|||||||
CROSS_IMAGE_NAME = docker-cli-cross$(IMAGE_TAG)
|
CROSS_IMAGE_NAME = docker-cli-cross$(IMAGE_TAG)
|
||||||
VALIDATE_IMAGE_NAME = docker-cli-shell-validate$(IMAGE_TAG)
|
VALIDATE_IMAGE_NAME = docker-cli-shell-validate$(IMAGE_TAG)
|
||||||
E2E_IMAGE_NAME = docker-cli-e2e$(IMAGE_TAG)
|
E2E_IMAGE_NAME = docker-cli-e2e$(IMAGE_TAG)
|
||||||
|
GO_BUILD_CACHE ?= y
|
||||||
MOUNTS = -v "$(CURDIR)":/go/src/github.com/docker/cli
|
MOUNTS = -v "$(CURDIR)":/go/src/github.com/docker/cli
|
||||||
|
CACHE_VOLUME_NAME := docker-cli-dev-cache
|
||||||
|
ifeq ($(GO_BUILD_CACHE),y)
|
||||||
|
MOUNTS += -v "$(CACHE_VOLUME_NAME):/root/.cache/go-build"
|
||||||
|
endif
|
||||||
VERSION = $(shell cat VERSION)
|
VERSION = $(shell cat VERSION)
|
||||||
ENVVARS = -e VERSION=$(VERSION) -e GITCOMMIT -e PLATFORM
|
ENVVARS = -e VERSION=$(VERSION) -e GITCOMMIT -e PLATFORM
|
||||||
|
|
||||||
# build docker image (dockerfiles/Dockerfile.build)
|
# build docker image (dockerfiles/Dockerfile.build)
|
||||||
.PHONY: build_docker_image
|
.PHONY: build_docker_image
|
||||||
build_docker_image:
|
build_docker_image:
|
||||||
docker build ${DOCKER_BUILD_ARGS} -t $(DEV_DOCKER_IMAGE_NAME) -f ./dockerfiles/Dockerfile.dev .
|
# build dockerfile from stdin so that we don't send the build-context; source is bind-mounted in the development environment
|
||||||
|
cat ./dockerfiles/Dockerfile.dev | docker build ${DOCKER_BUILD_ARGS} --build-arg=GO_VERSION -t $(DEV_DOCKER_IMAGE_NAME) -
|
||||||
|
|
||||||
# build docker image having the linting tools (dockerfiles/Dockerfile.lint)
|
# build docker image having the linting tools (dockerfiles/Dockerfile.lint)
|
||||||
.PHONY: build_linter_image
|
.PHONY: build_linter_image
|
||||||
build_linter_image:
|
build_linter_image:
|
||||||
docker build ${DOCKER_BUILD_ARGS} -t $(LINTER_IMAGE_NAME) -f ./dockerfiles/Dockerfile.lint .
|
# build dockerfile from stdin so that we don't send the build-context; source is bind-mounted in the development environment
|
||||||
|
cat ./dockerfiles/Dockerfile.lint | docker build ${DOCKER_BUILD_ARGS} --build-arg=GO_VERSION -t $(LINTER_IMAGE_NAME) -
|
||||||
|
|
||||||
.PHONY: build_cross_image
|
.PHONY: build_cross_image
|
||||||
build_cross_image:
|
build_cross_image:
|
||||||
docker build ${DOCKER_BUILD_ARGS} -t $(CROSS_IMAGE_NAME) -f ./dockerfiles/Dockerfile.cross .
|
# build dockerfile from stdin so that we don't send the build-context; source is bind-mounted in the development environment
|
||||||
|
cat ./dockerfiles/Dockerfile.cross | docker build ${DOCKER_BUILD_ARGS} --build-arg=GO_VERSION -t $(CROSS_IMAGE_NAME) -
|
||||||
|
|
||||||
.PHONY: build_shell_validate_image
|
.PHONY: build_shell_validate_image
|
||||||
build_shell_validate_image:
|
build_shell_validate_image:
|
||||||
docker build -t $(VALIDATE_IMAGE_NAME) -f ./dockerfiles/Dockerfile.shellcheck .
|
# build dockerfile from stdin so that we don't send the build-context; source is bind-mounted in the development environment
|
||||||
|
cat ./dockerfiles/Dockerfile.shellcheck | docker build --build-arg=GO_VERSION -t $(VALIDATE_IMAGE_NAME) -
|
||||||
|
|
||||||
.PHONY: build_binary_native_image
|
.PHONY: build_binary_native_image
|
||||||
build_binary_native_image:
|
build_binary_native_image:
|
||||||
docker build -t $(BINARY_NATIVE_IMAGE_NAME) -f ./dockerfiles/Dockerfile.binary-native .
|
# build dockerfile from stdin so that we don't send the build-context; source is bind-mounted in the development environment
|
||||||
|
cat ./dockerfiles/Dockerfile.binary-native | docker build --build-arg=GO_VERSION -t $(BINARY_NATIVE_IMAGE_NAME) -
|
||||||
|
|
||||||
.PHONY: build_e2e_image
|
.PHONY: build_e2e_image
|
||||||
build_e2e_image:
|
build_e2e_image:
|
||||||
docker build -t $(E2E_IMAGE_NAME) --build-arg VERSION=$(VERSION) --build-arg GITCOMMIT=$(GITCOMMIT) -f ./dockerfiles/Dockerfile.e2e .
|
docker build -t $(E2E_IMAGE_NAME) --build-arg=GO_VERSION --build-arg VERSION=$(VERSION) --build-arg GITCOMMIT=$(GITCOMMIT) -f ./dockerfiles/Dockerfile.e2e .
|
||||||
|
|
||||||
|
|
||||||
binary: build_binary_native_image ## build the CLI
|
binary: build_binary_native_image ## build the CLI
|
||||||
@@ -49,6 +59,7 @@ build: binary ## alias for binary
|
|||||||
.PHONY: clean
|
.PHONY: clean
|
||||||
clean: build_docker_image ## clean build artifacts
|
clean: build_docker_image ## clean build artifacts
|
||||||
docker run --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make clean
|
docker run --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make clean
|
||||||
|
docker volume rm -f $(CACHE_VOLUME_NAME)
|
||||||
|
|
||||||
.PHONY: test-unit
|
.PHONY: test-unit
|
||||||
test-unit: build_docker_image # run unit tests (using go test)
|
test-unit: build_docker_image # run unit tests (using go test)
|
||||||
@@ -81,6 +92,10 @@ shell: dev ## alias for dev
|
|||||||
lint: build_linter_image ## run linters
|
lint: build_linter_image ## run linters
|
||||||
docker run -ti $(ENVVARS) $(MOUNTS) $(LINTER_IMAGE_NAME)
|
docker run -ti $(ENVVARS) $(MOUNTS) $(LINTER_IMAGE_NAME)
|
||||||
|
|
||||||
|
.PHONY: fmt
|
||||||
|
fmt:
|
||||||
|
docker run --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make fmt
|
||||||
|
|
||||||
.PHONY: vendor
|
.PHONY: vendor
|
||||||
vendor: build_docker_image vendor.conf ## download dependencies (vendor/) listed in vendor.conf
|
vendor: build_docker_image vendor.conf ## download dependencies (vendor/) listed in vendor.conf
|
||||||
docker run -ti --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make vendor
|
docker run -ti --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make vendor
|
||||||
@@ -105,7 +120,7 @@ shellcheck: build_shell_validate_image ## run shellcheck validation
|
|||||||
docker run -ti --rm $(ENVVARS) $(MOUNTS) $(VALIDATE_IMAGE_NAME) make shellcheck
|
docker run -ti --rm $(ENVVARS) $(MOUNTS) $(VALIDATE_IMAGE_NAME) make shellcheck
|
||||||
|
|
||||||
.PHONY: test-e2e ## run e2e tests
|
.PHONY: test-e2e ## run e2e tests
|
||||||
test-e2e: test-e2e-non-experimental test-e2e-experimental test-e2e-containerized
|
test-e2e: test-e2e-non-experimental test-e2e-experimental test-e2e-connhelper-ssh
|
||||||
|
|
||||||
.PHONY: test-e2e-experimental
|
.PHONY: test-e2e-experimental
|
||||||
test-e2e-experimental: build_e2e_image
|
test-e2e-experimental: build_e2e_image
|
||||||
@@ -115,13 +130,9 @@ test-e2e-experimental: build_e2e_image
|
|||||||
test-e2e-non-experimental: build_e2e_image
|
test-e2e-non-experimental: build_e2e_image
|
||||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock $(E2E_IMAGE_NAME)
|
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock $(E2E_IMAGE_NAME)
|
||||||
|
|
||||||
.PHONY: test-e2e-containerized
|
.PHONY: test-e2e-connhelper-ssh
|
||||||
test-e2e-containerized: build_e2e_image
|
test-e2e-connhelper-ssh: build_e2e_image
|
||||||
docker run --rm --privileged \
|
docker run -e TEST_CONNHELPER=ssh -e DOCKERD_EXPERIMENTAL=1 --rm -v /var/run/docker.sock:/var/run/docker.sock $(E2E_IMAGE_NAME)
|
||||||
-v /var/lib/docker \
|
|
||||||
-v /var/lib/containerd \
|
|
||||||
-v /lib/modules:/lib/modules \
|
|
||||||
$(E2E_IMAGE_NAME) /go/src/github.com/docker/cli/scripts/test/engine/entry
|
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help: ## print this help
|
help: ## print this help
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
FROM golang:1.10.3-alpine
|
ARG GO_VERSION=1.12.12
|
||||||
|
|
||||||
|
FROM golang:${GO_VERSION}-alpine
|
||||||
|
|
||||||
RUN apk add -U git bash coreutils gcc musl-dev
|
RUN apk add -U git bash coreutils gcc musl-dev
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
FROM dockercore/golang-cross:1.10.3@sha256:7671b4ed357fda50124e5679d36c4c3206ded4d43f1d2e0ff3d120a1e2bf94d7
|
ARG GO_VERSION=1.12.12
|
||||||
|
|
||||||
|
FROM dockercore/golang-cross:${GO_VERSION}
|
||||||
ENV DISABLE_WARN_OUTSIDE_CONTAINER=1
|
ENV DISABLE_WARN_OUTSIDE_CONTAINER=1
|
||||||
WORKDIR /go/src/github.com/docker/cli
|
WORKDIR /go/src/github.com/docker/cli
|
||||||
|
COPY . .
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
ARG GO_VERSION=1.12.12
|
||||||
|
|
||||||
FROM golang:1.10.3-alpine
|
FROM golang:${GO_VERSION}-alpine
|
||||||
|
|
||||||
RUN apk add -U git make bash coreutils ca-certificates curl
|
RUN apk add -U git make bash coreutils ca-certificates curl
|
||||||
|
|
||||||
ARG VNDR_SHA=1fc68ee0c852556a9ed53cbde16247033f104111
|
ARG VNDR_SHA=b177b583eb9d44bd5abfca3083a4aeb971b75861
|
||||||
RUN go get -d github.com/LK4D4/vndr && \
|
RUN go get -d github.com/LK4D4/vndr && \
|
||||||
cd /go/src/github.com/LK4D4/vndr && \
|
cd /go/src/github.com/LK4D4/vndr && \
|
||||||
git checkout -q "$VNDR_SHA" && \
|
git checkout -q "$VNDR_SHA" && \
|
||||||
@@ -22,3 +23,4 @@ ENV CGO_ENABLED=0 \
|
|||||||
DISABLE_WARN_OUTSIDE_CONTAINER=1
|
DISABLE_WARN_OUTSIDE_CONTAINER=1
|
||||||
WORKDIR /go/src/github.com/docker/cli
|
WORKDIR /go/src/github.com/docker/cli
|
||||||
CMD sh
|
CMD sh
|
||||||
|
COPY . .
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
ARG GO_VERSION=1.10.3
|
ARG GO_VERSION=1.12.12
|
||||||
|
|
||||||
FROM docker/containerd-shim-process:a4d1531 AS containerd-shim-process
|
FROM docker/containerd-shim-process:a4d1531 AS containerd-shim-process
|
||||||
|
|
||||||
@@ -13,30 +13,9 @@ RUN apt-get update && apt-get install -y \
|
|||||||
libapparmor-dev \
|
libapparmor-dev \
|
||||||
libseccomp-dev \
|
libseccomp-dev \
|
||||||
iptables \
|
iptables \
|
||||||
|
openssh-client \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# TODO - consider replacing with an official image and a multi-stage build to pluck the binaries out
|
|
||||||
#ARG CONTAINERD_VERSION=v1.1.2
|
|
||||||
#ARG CONTAINERD_VERSION=47a128d
|
|
||||||
#ARG CONTAINERD_VERSION=6c3e782f
|
|
||||||
ARG CONTAINERD_VERSION=65839a47a88b0a1c5dc34981f1741eccefc9f2b0
|
|
||||||
RUN git clone https://github.com/containerd/containerd.git /go/src/github.com/containerd/containerd && \
|
|
||||||
cd /go/src/github.com/containerd/containerd && \
|
|
||||||
git checkout ${CONTAINERD_VERSION} && \
|
|
||||||
make && \
|
|
||||||
make install
|
|
||||||
COPY e2eengine/config.toml /etc/containerd/config.toml
|
|
||||||
COPY --from=containerd-shim-process /bin/containerd-shim-process-v1 /bin/
|
|
||||||
|
|
||||||
|
|
||||||
# TODO - consider replacing with an official image and a multi-stage build to pluck the binaries out
|
|
||||||
ARG RUNC_VERSION=v1.0.0-rc5
|
|
||||||
RUN git clone https://github.com/opencontainers/runc.git /go/src/github.com/opencontainers/runc && \
|
|
||||||
cd /go/src/github.com/opencontainers/runc && \
|
|
||||||
git checkout ${RUNC_VERSION} && \
|
|
||||||
make && \
|
|
||||||
make install
|
|
||||||
|
|
||||||
ARG COMPOSE_VERSION=1.21.2
|
ARG COMPOSE_VERSION=1.21.2
|
||||||
RUN curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose \
|
RUN curl -L https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose \
|
||||||
&& chmod +x /usr/local/bin/docker-compose
|
&& chmod +x /usr/local/bin/docker-compose
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
FROM golang:1.10.3-alpine
|
ARG GO_VERSION=1.12.12
|
||||||
|
|
||||||
|
FROM golang:${GO_VERSION}-alpine
|
||||||
|
|
||||||
RUN apk add -U git
|
RUN apk add -U git
|
||||||
|
|
||||||
@@ -15,3 +17,4 @@ ENV CGO_ENABLED=0
|
|||||||
ENV DISABLE_WARN_OUTSIDE_CONTAINER=1
|
ENV DISABLE_WARN_OUTSIDE_CONTAINER=1
|
||||||
ENTRYPOINT ["/usr/local/bin/gometalinter"]
|
ENTRYPOINT ["/usr/local/bin/gometalinter"]
|
||||||
CMD ["--config=gometalinter.json", "./..."]
|
CMD ["--config=gometalinter.json", "./..."]
|
||||||
|
COPY . .
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
FROM debian:stretch-slim
|
FROM koalaman/shellcheck-alpine:v0.6.0
|
||||||
|
RUN apk add --no-cache bash make
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get -y install make shellcheck && \
|
|
||||||
apt-get clean
|
|
||||||
|
|
||||||
WORKDIR /go/src/github.com/docker/cli
|
WORKDIR /go/src/github.com/docker/cli
|
||||||
ENV DISABLE_WARN_OUTSIDE_CONTAINER=1
|
ENV DISABLE_WARN_OUTSIDE_CONTAINER=1
|
||||||
CMD bash
|
COPY . .
|
||||||
|
|||||||
+32
-1
@@ -19,6 +19,37 @@ The following list of features are deprecated in Engine.
|
|||||||
To learn more about Docker Engine's deprecation policy,
|
To learn more about Docker Engine's deprecation policy,
|
||||||
see [Feature Deprecation Policy](https://docs.docker.com/engine/#feature-deprecation-policy).
|
see [Feature Deprecation Policy](https://docs.docker.com/engine/#feature-deprecation-policy).
|
||||||
|
|
||||||
|
### Legacy "overlay" storage driver
|
||||||
|
|
||||||
|
**Deprecated in Release: v18.09.0**
|
||||||
|
|
||||||
|
The `overlay` storage driver is deprecated in favor of the `overlay2` storage
|
||||||
|
driver, which has all the benefits of `overlay`, without its limitations (excessive
|
||||||
|
inode consumption). The legacy `overlay` storage driver will be removed in a future
|
||||||
|
release. Users of the `overlay` storage driver should migrate to the `overlay2`
|
||||||
|
storage driver.
|
||||||
|
|
||||||
|
The legacy `overlay` storage driver allowed using overlayFS-backed filesystems
|
||||||
|
on pre 4.x kernels. Now that all supported distributions are able to run `overlay2`
|
||||||
|
(as they are either on kernel 4.x, or have support for multiple lowerdirs
|
||||||
|
backported), there is no reason to keep maintaining the `overlay` storage driver.
|
||||||
|
|
||||||
|
### device mapper storage driver
|
||||||
|
|
||||||
|
**Deprecated in Release: v18.09.0**
|
||||||
|
|
||||||
|
The `devicemapper` storage driver is deprecated in favor of `overlay2`, and will
|
||||||
|
be removed in a future release. Users of the `devicemapper` storage driver are
|
||||||
|
recommended to migrate to a different storage driver, such as `overlay2`, which
|
||||||
|
is now the default storage driver.
|
||||||
|
|
||||||
|
The `devicemapper` storage driver facilitates running Docker on older (3.x) kernels
|
||||||
|
that have no support for other storage drivers (such as overlay2, or AUFS).
|
||||||
|
|
||||||
|
Now that support for `overlay2` is added to all supported distros (as they are
|
||||||
|
either on kernel 4.x, or have support for multiple lowerdirs backported), there
|
||||||
|
is no reason to continue maintenance of the `devicemapper` storage driver.
|
||||||
|
|
||||||
### Reserved namespaces in engine labels
|
### Reserved namespaces in engine labels
|
||||||
|
|
||||||
**Deprecated in Release: v18.06.0**
|
**Deprecated in Release: v18.06.0**
|
||||||
@@ -167,7 +198,7 @@ The docker login command is removing the ability to automatically register for a
|
|||||||
|
|
||||||
**Target For Removal In Release: v17.06**
|
**Target For Removal In Release: v17.06**
|
||||||
|
|
||||||
The flag `--security-opt` doesn't use the colon separator(`:`) anymore to divide keys and values, it uses the equal symbol(`=`) for consistency with other similar flags, like `--storage-opt`.
|
The flag `--security-opt` doesn't use the colon separator (`:`) anymore to divide keys and values, it uses the equal symbol (`=`) for consistency with other similar flags, like `--storage-opt`.
|
||||||
|
|
||||||
### `/containers/(id or name)/copy` endpoint
|
### `/containers/(id or name)/copy` endpoint
|
||||||
|
|
||||||
|
|||||||
+122
-2
@@ -121,6 +121,28 @@ registries.
|
|||||||
When you're done with your build, you're ready to look into [*Pushing a
|
When you're done with your build, you're ready to look into [*Pushing a
|
||||||
repository to its registry*](https://docs.docker.com/engine/tutorials/dockerrepos/#/contributing-to-docker-hub).
|
repository to its registry*](https://docs.docker.com/engine/tutorials/dockerrepos/#/contributing-to-docker-hub).
|
||||||
|
|
||||||
|
|
||||||
|
## BuildKit
|
||||||
|
|
||||||
|
Starting with version 18.09, Docker supports a new backend for executing your
|
||||||
|
builds that is provided by the [moby/buildkit](https://github.com/moby/buildkit)
|
||||||
|
project. The BuildKit backend provides many benefits compared to the old
|
||||||
|
implementation. For example, BuildKit can:
|
||||||
|
|
||||||
|
* Detect and skip executing unused build stages
|
||||||
|
* Parallelize building independent build stages
|
||||||
|
* Incrementally transfer only the changed files in your build context between builds
|
||||||
|
* Detect and skip transferring unused files in your build context
|
||||||
|
* Use external Dockerfile implementations with many new features
|
||||||
|
* Avoid side-effects with rest of the API (intermediate images and containers)
|
||||||
|
* Prioritize your build cache for automatic pruning
|
||||||
|
|
||||||
|
To use the BuildKit backend, you need to set an environment variable
|
||||||
|
`DOCKER_BUILDKIT=1` on the CLI before invoking `docker build`.
|
||||||
|
|
||||||
|
To learn about the experimental Dockerfile syntax available to BuildKit-based
|
||||||
|
builds [refer to the documentation in the BuildKit repository](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md).
|
||||||
|
|
||||||
## Format
|
## Format
|
||||||
|
|
||||||
Here is the format of the `Dockerfile`:
|
Here is the format of the `Dockerfile`:
|
||||||
@@ -224,10 +246,64 @@ following lines are all treated identically:
|
|||||||
# dIrEcTiVe=value
|
# dIrEcTiVe=value
|
||||||
```
|
```
|
||||||
|
|
||||||
The following parser directive is supported:
|
The following parser directives are supported:
|
||||||
|
|
||||||
|
* `syntax`
|
||||||
* `escape`
|
* `escape`
|
||||||
|
|
||||||
|
## syntax
|
||||||
|
|
||||||
|
# syntax=[remote image reference]
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
# syntax=docker/dockerfile
|
||||||
|
# syntax=docker/dockerfile:1.0
|
||||||
|
# syntax=docker.io/docker/dockerfile:1
|
||||||
|
# syntax=docker/dockerfile:1.0.0-experimental
|
||||||
|
# syntax=example.com/user/repo:tag@sha256:abcdef...
|
||||||
|
|
||||||
|
This feature is only enabled if the [BuildKit](#buildkit) backend is used.
|
||||||
|
|
||||||
|
The syntax directive defines the location of the Dockerfile builder that is used for
|
||||||
|
building the current Dockerfile. The BuildKit backend allows to seamlessly use
|
||||||
|
external implementations of builders that are distributed as Docker images and
|
||||||
|
execute inside a container sandbox environment.
|
||||||
|
|
||||||
|
Custom Dockerfile implementation allows you to:
|
||||||
|
- Automatically get bugfixes without updating the daemon
|
||||||
|
- Make sure all users are using the same implementation to build your Dockerfile
|
||||||
|
- Use the latest features without updating the daemon
|
||||||
|
- Try out new experimental or third-party features
|
||||||
|
|
||||||
|
### Official releases
|
||||||
|
|
||||||
|
Docker distributes official versions of the images that can be used for building
|
||||||
|
Dockerfiles under `docker/dockerfile` repository on Docker Hub. There are two
|
||||||
|
channels where new images are released: stable and experimental.
|
||||||
|
|
||||||
|
Stable channel follows semantic versioning. For example:
|
||||||
|
|
||||||
|
- docker/dockerfile:1.0.0 - only allow immutable version 1.0.0
|
||||||
|
- docker/dockerfile:1.0 - allow versions 1.0.*
|
||||||
|
- docker/dockerfile:1 - allow versions 1.*.*
|
||||||
|
- docker/dockerfile:latest - latest release on stable channel
|
||||||
|
|
||||||
|
The experimental channel uses incremental versioning with the major and minor
|
||||||
|
component from the stable channel on the time of the release. For example:
|
||||||
|
|
||||||
|
- docker/dockerfile:1.0.1-experimental - only allow immutable version 1.0.1-experimental
|
||||||
|
- docker/dockerfile:1.0-experimental - latest experimental releases after 1.0
|
||||||
|
- docker/dockerfile:experimental - latest release on experimental channel
|
||||||
|
|
||||||
|
You should choose a channel that best fits your needs. If you only want
|
||||||
|
bugfixes, you should use `docker/dockerfile:1.0`. If you want to benefit from
|
||||||
|
experimental features, you should use the experimental channel. If you are using
|
||||||
|
the experimental channel, newer releases may not be backwards compatible, so it
|
||||||
|
is recommended to use an immutable full version variant.
|
||||||
|
|
||||||
|
For master builds and nightly feature releases refer to the description in [the source repository](https://github.com/moby/buildkit/blob/master/README.md).
|
||||||
|
|
||||||
## escape
|
## escape
|
||||||
|
|
||||||
# escape=\ (backslash)
|
# escape=\ (backslash)
|
||||||
@@ -1339,6 +1415,10 @@ The table below shows what command is executed for different `ENTRYPOINT` / `CMD
|
|||||||
| **CMD ["p1_cmd", "p2_cmd"]** | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd |
|
| **CMD ["p1_cmd", "p2_cmd"]** | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd |
|
||||||
| **CMD exec_cmd p1_cmd** | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
|
| **CMD exec_cmd p1_cmd** | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
|
||||||
|
|
||||||
|
> **Note**: If `CMD` is defined from the base image, setting `ENTRYPOINT` will
|
||||||
|
> reset `CMD` to an empty value. In this scenario, `CMD` must be defined in the
|
||||||
|
> current image to have a value.
|
||||||
|
|
||||||
## VOLUME
|
## VOLUME
|
||||||
|
|
||||||
VOLUME ["/data"]
|
VOLUME ["/data"]
|
||||||
@@ -1379,7 +1459,7 @@ Keep the following things in mind about volumes in the `Dockerfile`.
|
|||||||
data within the volume after it has been declared, those changes will be discarded.
|
data within the volume after it has been declared, those changes will be discarded.
|
||||||
|
|
||||||
- **JSON formatting**: The list is parsed as a JSON array.
|
- **JSON formatting**: The list is parsed as a JSON array.
|
||||||
You must enclose words with double quotes (`"`)rather than single quotes (`'`).
|
You must enclose words with double quotes (`"`) rather than single quotes (`'`).
|
||||||
|
|
||||||
- **The host directory is declared at container run-time**: The host directory
|
- **The host directory is declared at container run-time**: The host directory
|
||||||
(the mountpoint) is, by its nature, host-dependent. This is to preserve image
|
(the mountpoint) is, by its nature, host-dependent. This is to preserve image
|
||||||
@@ -1623,6 +1703,38 @@ RUN echo "Hello World"
|
|||||||
When building this Dockerfile, the `HTTP_PROXY` is preserved in the
|
When building this Dockerfile, the `HTTP_PROXY` is preserved in the
|
||||||
`docker history`, and changing its value invalidates the build cache.
|
`docker history`, and changing its value invalidates the build cache.
|
||||||
|
|
||||||
|
### Automatic platform ARGs in the global scope
|
||||||
|
|
||||||
|
This feature is only available when using the [BuildKit](#buildkit) backend.
|
||||||
|
|
||||||
|
Docker predefines a set of `ARG` variables with information on the platform of
|
||||||
|
the node performing the build (build platform) and on the platform of the
|
||||||
|
resulting image (target platform). The target platform can be specified with
|
||||||
|
the `--platform` flag on `docker build`.
|
||||||
|
|
||||||
|
The following `ARG` variables are set automatically:
|
||||||
|
|
||||||
|
* `TARGETPLATFORM` - platform of the build result. Eg `linux/amd64`, `linux/arm/v7`, `windows/amd64`.
|
||||||
|
* `TARGETOS` - OS component of TARGETPLATFORM
|
||||||
|
* `TARGETARCH` - architecture component of TARGETPLATFORM
|
||||||
|
* `TARGETVARIANT` - variant component of TARGETPLATFORM
|
||||||
|
* `BUILDPLATFORM` - platform of the node performing the build.
|
||||||
|
* `BUILDOS` - OS component of BUILDPLATFORM
|
||||||
|
* `BUILDARCH` - OS component of BUILDPLATFORM
|
||||||
|
* `BUILDVARIANT` - OS component of BUILDPLATFORM
|
||||||
|
|
||||||
|
These arguments are defined in the global scope so are not automatically
|
||||||
|
available inside build stages or for your `RUN` commands. To expose one of
|
||||||
|
these arguments inside the build stage redefine it without value.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
FROM alpine
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
RUN echo "I'm building for $TARGETPLATFORM"
|
||||||
|
```
|
||||||
|
|
||||||
### Impact on build caching
|
### Impact on build caching
|
||||||
|
|
||||||
`ARG` variables are not persisted into the built image as `ENV` variables are.
|
`ARG` variables are not persisted into the built image as `ENV` variables are.
|
||||||
@@ -1931,6 +2043,14 @@ required such as `zsh`, `csh`, `tcsh` and others.
|
|||||||
|
|
||||||
The `SHELL` feature was added in Docker 1.12.
|
The `SHELL` feature was added in Docker 1.12.
|
||||||
|
|
||||||
|
## External implementation features
|
||||||
|
|
||||||
|
This feature is only available when using the [BuildKit](#buildkit) backend.
|
||||||
|
|
||||||
|
Docker build supports experimental features like cache mounts, build secrets and
|
||||||
|
ssh forwarding that are enabled by using an external implementation of the
|
||||||
|
builder with a syntax directive. To learn about these features, [refer to the documentation in BuildKit repository](https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md).
|
||||||
|
|
||||||
## Dockerfile examples
|
## Dockerfile examples
|
||||||
|
|
||||||
Below you can see some examples of Dockerfile syntax. If you're interested in
|
Below you can see some examples of Dockerfile syntax. If you're interested in
|
||||||
|
|||||||
@@ -44,8 +44,8 @@ from different sessions on the Docker host.
|
|||||||
|
|
||||||
To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the
|
To stop a container, use `CTRL-c`. This key sequence sends `SIGKILL` to the
|
||||||
container. If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to
|
container. If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` to
|
||||||
the container. You can detach from a container and leave it running using the
|
the container. If the container was run with `-i` and `-t`, you can detach from
|
||||||
`CTRL-p CTRL-q` key sequence.
|
a container and leave it running using the `CTRL-p CTRL-q` key sequence.
|
||||||
|
|
||||||
> **Note:**
|
> **Note:**
|
||||||
> A process running as PID 1 inside a container is treated specially by
|
> A process running as PID 1 inside a container is treated specially by
|
||||||
|
|||||||
@@ -48,14 +48,18 @@ Options:
|
|||||||
'<network-name>|<network-id>': connect to a user-defined network
|
'<network-name>|<network-id>': connect to a user-defined network
|
||||||
--no-cache Do not use cache when building the image
|
--no-cache Do not use cache when building the image
|
||||||
--pull Always attempt to pull a newer version of the image
|
--pull Always attempt to pull a newer version of the image
|
||||||
|
--progress Set type of progress output (only if BuildKit enabled) (auto, plain, tty).
|
||||||
|
Use plain to show container output
|
||||||
-q, --quiet Suppress the build output and print image ID on success
|
-q, --quiet Suppress the build output and print image ID on success
|
||||||
--rm Remove intermediate containers after a successful build (default true)
|
--rm Remove intermediate containers after a successful build (default true)
|
||||||
|
--secret Secret file to expose to the build (only if BuildKit enabled): id=mysecret,src=/local/secret"
|
||||||
--security-opt value Security Options (default [])
|
--security-opt value Security Options (default [])
|
||||||
--shm-size bytes Size of /dev/shm
|
--shm-size bytes Size of /dev/shm
|
||||||
The format is `<number><unit>`. `number` must be greater than `0`.
|
The format is `<number><unit>`. `number` must be greater than `0`.
|
||||||
Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes),
|
Unit is optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes),
|
||||||
or `g` (gigabytes). If you omit the unit, the system uses bytes.
|
or `g` (gigabytes). If you omit the unit, the system uses bytes.
|
||||||
--squash Squash newly built layers into a single new layer (**Experimental Only**)
|
--squash Squash newly built layers into a single new layer (**Experimental Only**)
|
||||||
|
--ssh SSH agent socket or keys to expose to the build (only if BuildKit enabled) (format: default|<id>[=<socket>|<key>[,<key>]])
|
||||||
-t, --tag value Name and optionally a tag in the 'name:tag' format (default [])
|
-t, --tag value Name and optionally a tag in the 'name:tag' format (default [])
|
||||||
--target string Set the target build stage to build.
|
--target string Set the target build stage to build.
|
||||||
--ulimit value Ulimit options (default [])
|
--ulimit value Ulimit options (default [])
|
||||||
@@ -500,13 +504,13 @@ stable.
|
|||||||
|
|
||||||
|
|
||||||
Squashing layers can be beneficial if your Dockerfile produces multiple layers
|
Squashing layers can be beneficial if your Dockerfile produces multiple layers
|
||||||
modifying the same files, for example, file that are created in one step, and
|
modifying the same files, for example, files that are created in one step, and
|
||||||
removed in another step. For other use-cases, squashing images may actually have
|
removed in another step. For other use-cases, squashing images may actually have
|
||||||
a negative impact on performance; when pulling an image consisting of multiple
|
a negative impact on performance; when pulling an image consisting of multiple
|
||||||
layers, layers can be pulled in parallel, and allows sharing layers between
|
layers, layers can be pulled in parallel, and allows sharing layers between
|
||||||
images (saving space).
|
images (saving space).
|
||||||
|
|
||||||
For most use cases, multi-stage are a better alternative, as they give more
|
For most use cases, multi-stage builds are a better alternative, as they give more
|
||||||
fine-grained control over your build, and can take advantage of future
|
fine-grained control over your build, and can take advantage of future
|
||||||
optimizations in the builder. Refer to the [use multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/)
|
optimizations in the builder. Refer to the [use multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/)
|
||||||
section in the userguide for more information.
|
section in the userguide for more information.
|
||||||
@@ -527,7 +531,7 @@ The `--squash` option has a number of known limitations:
|
|||||||
downloading a single layer cannot be parallelized.
|
downloading a single layer cannot be parallelized.
|
||||||
- When attempting to squash an image that does not make changes to the
|
- When attempting to squash an image that does not make changes to the
|
||||||
filesystem (for example, the Dockerfile only contains `ENV` instructions),
|
filesystem (for example, the Dockerfile only contains `ENV` instructions),
|
||||||
the squash step will fail (see [issue #33823](https://github.com/moby/moby/issues/33823)
|
the squash step will fail (see [issue #33823](https://github.com/moby/moby/issues/33823)).
|
||||||
|
|
||||||
#### Prerequisites
|
#### Prerequisites
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ Options:
|
|||||||
--memory-reservation string Memory soft limit
|
--memory-reservation string Memory soft limit
|
||||||
--memory-swap string Swap limit equal to memory plus swap: '-1' to enable unlimited swap
|
--memory-swap string Swap limit equal to memory plus swap: '-1' to enable unlimited swap
|
||||||
--memory-swappiness int Tune container memory swappiness (0 to 100) (default -1)
|
--memory-swappiness int Tune container memory swappiness (0 to 100) (default -1)
|
||||||
--mount value Attach a filesytem mount to the container (default [])
|
--mount value Attach a filesystem mount to the container (default [])
|
||||||
--name string Assign a name to the container
|
--name string Assign a name to the container
|
||||||
--network-alias value Add network-scoped alias for the container (default [])
|
--network-alias value Add network-scoped alias for the container (default [])
|
||||||
--network string Connect a container to a network (default "default")
|
--network string Connect a container to a network (default "default")
|
||||||
@@ -256,5 +256,5 @@ docker create --device-cgroup-rule='c 42:* rmw' -name my-container my-image
|
|||||||
Then, a user could ask `udev` to execute a script that would `docker exec my-container mknod newDevX c 42 <minor>`
|
Then, a user could ask `udev` to execute a script that would `docker exec my-container mknod newDevX c 42 <minor>`
|
||||||
the required device when it is added.
|
the required device when it is added.
|
||||||
|
|
||||||
NOTE: initially present devices still need to be explicitely added to
|
NOTE: initially present devices still need to be explicitly added to
|
||||||
the create/run command
|
the create/run command
|
||||||
|
|||||||
@@ -191,7 +191,10 @@ $ docker -H ssh://example.com ps
|
|||||||
```
|
```
|
||||||
|
|
||||||
To use SSH connection, you need to set up `ssh` so that it can reach the
|
To use SSH connection, you need to set up `ssh` so that it can reach the
|
||||||
remote host with public key authentication.
|
remote host with public key authentication. Password authentication is not
|
||||||
|
supported. If your key is protected with passphrase, you need to set up
|
||||||
|
`ssh-agent`.
|
||||||
|
|
||||||
Also, you need to have `docker` binary 18.09 or later on the daemon host.
|
Also, you need to have `docker` binary 18.09 or later on the daemon host.
|
||||||
|
|
||||||
#### Bind Docker to another host/port or a Unix socket
|
#### Bind Docker to another host/port or a Unix socket
|
||||||
@@ -300,7 +303,7 @@ the same file can share a single page cache entry (or entries), it makes
|
|||||||
> **Note**: As promising as `overlay` is, the feature is still quite young and
|
> **Note**: As promising as `overlay` is, the feature is still quite young and
|
||||||
> should not be used in production. Most notably, using `overlay` can cause
|
> should not be used in production. Most notably, using `overlay` can cause
|
||||||
> excessive inode consumption (especially as the number of images grows), as
|
> excessive inode consumption (especially as the number of images grows), as
|
||||||
> well as > being incompatible with the use of RPMs.
|
> well as being incompatible with the use of RPMs.
|
||||||
|
|
||||||
The `overlay2` uses the same fast union filesystem but takes advantage of
|
The `overlay2` uses the same fast union filesystem but takes advantage of
|
||||||
[additional features](https://lkml.org/lkml/2015/2/11/106) added in Linux
|
[additional features](https://lkml.org/lkml/2015/2/11/106) added in Linux
|
||||||
@@ -1228,10 +1231,14 @@ The `--metrics-addr` option takes a tcp address to serve the metrics API.
|
|||||||
This feature is still experimental, therefore, the daemon must be running in experimental
|
This feature is still experimental, therefore, the daemon must be running in experimental
|
||||||
mode for this feature to work.
|
mode for this feature to work.
|
||||||
|
|
||||||
To serve the metrics API on localhost:1337 you would specify `--metrics-addr 127.0.0.1:1337`
|
To serve the metrics API on `localhost:9323` you would specify `--metrics-addr 127.0.0.1:9323`,
|
||||||
allowing you to make requests on the API at `127.0.0.1:1337/metrics` to receive metrics in the
|
allowing you to make requests on the API at `127.0.0.1:9323/metrics` to receive metrics in the
|
||||||
[prometheus](https://prometheus.io/docs/instrumenting/exposition_formats/) format.
|
[prometheus](https://prometheus.io/docs/instrumenting/exposition_formats/) format.
|
||||||
|
|
||||||
|
Port `9323` is the [default port associated with Docker
|
||||||
|
metrics](https://github.com/prometheus/prometheus/wiki/Default-port-allocations)
|
||||||
|
to avoid collisions with other prometheus exporters and services.
|
||||||
|
|
||||||
If you are running a prometheus server you can add this address to your scrape configs
|
If you are running a prometheus server you can add this address to your scrape configs
|
||||||
to have prometheus collect metrics on Docker. For more information
|
to have prometheus collect metrics on Docker. For more information
|
||||||
on prometheus you can view the website [here](https://prometheus.io/).
|
on prometheus you can view the website [here](https://prometheus.io/).
|
||||||
@@ -1240,7 +1247,7 @@ on prometheus you can view the website [here](https://prometheus.io/).
|
|||||||
scrape_configs:
|
scrape_configs:
|
||||||
- job_name: 'docker'
|
- job_name: 'docker'
|
||||||
static_configs:
|
static_configs:
|
||||||
- targets: ['127.0.0.1:1337']
|
- targets: ['127.0.0.1:9323']
|
||||||
```
|
```
|
||||||
|
|
||||||
Please note that this feature is still marked as experimental as metrics and metric
|
Please note that this feature is still marked as experimental as metrics and metric
|
||||||
@@ -1297,12 +1304,18 @@ This is a full example of the allowed configuration options on Linux:
|
|||||||
"exec-opts": [],
|
"exec-opts": [],
|
||||||
"exec-root": "",
|
"exec-root": "",
|
||||||
"experimental": false,
|
"experimental": false,
|
||||||
|
"features": {},
|
||||||
"storage-driver": "",
|
"storage-driver": "",
|
||||||
"storage-opts": [],
|
"storage-opts": [],
|
||||||
"labels": [],
|
"labels": [],
|
||||||
"live-restore": true,
|
"live-restore": true,
|
||||||
"log-driver": "",
|
"log-driver": "json-file",
|
||||||
"log-opts": {},
|
"log-opts": {
|
||||||
|
"max-size": "10m",
|
||||||
|
"max-file":"5",
|
||||||
|
"labels": "somelabel",
|
||||||
|
"env": "os,customer"
|
||||||
|
},
|
||||||
"mtu": 0,
|
"mtu": 0,
|
||||||
"pidfile": "",
|
"pidfile": "",
|
||||||
"cluster-store": "",
|
"cluster-store": "",
|
||||||
@@ -1326,7 +1339,13 @@ This is a full example of the allowed configuration options on Linux:
|
|||||||
"userns-remap": "",
|
"userns-remap": "",
|
||||||
"group": "",
|
"group": "",
|
||||||
"cgroup-parent": "",
|
"cgroup-parent": "",
|
||||||
"default-ulimits": {},
|
"default-ulimits": {
|
||||||
|
"nofile": {
|
||||||
|
"Name": "nofile",
|
||||||
|
"Hard": 64000,
|
||||||
|
"Soft": 64000
|
||||||
|
}
|
||||||
|
},
|
||||||
"init": false,
|
"init": false,
|
||||||
"init-path": "/usr/libexec/docker-init",
|
"init-path": "/usr/libexec/docker-init",
|
||||||
"ipv6": false,
|
"ipv6": false,
|
||||||
@@ -1392,6 +1411,7 @@ This is a full example of the allowed configuration options on Windows:
|
|||||||
"dns-search": [],
|
"dns-search": [],
|
||||||
"exec-opts": [],
|
"exec-opts": [],
|
||||||
"experimental": false,
|
"experimental": false,
|
||||||
|
"features":{},
|
||||||
"storage-driver": "",
|
"storage-driver": "",
|
||||||
"storage-opts": [],
|
"storage-opts": [],
|
||||||
"labels": [],
|
"labels": [],
|
||||||
@@ -1422,6 +1442,16 @@ This is a full example of the allowed configuration options on Windows:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Feature options
|
||||||
|
The optional field `features` in `daemon.json` allows users to enable or disable specific
|
||||||
|
daemon features. For example, `{"features":{"buildkit": true}}` enables `buildkit` as the
|
||||||
|
default docker image builder.
|
||||||
|
|
||||||
|
The list of currently supported feature options:
|
||||||
|
- `buildkit`: It enables `buildkit` as default builder when set to `true` or disables it by
|
||||||
|
`false`. Note that if this option is not explicitly set in the daemon config file, then it
|
||||||
|
is up to the cli to determine which builder to invoke.
|
||||||
|
|
||||||
#### Configuration reload behavior
|
#### Configuration reload behavior
|
||||||
|
|
||||||
Some options can be reconfigured when the daemon is running without requiring
|
Some options can be reconfigured when the daemon is running without requiring
|
||||||
@@ -1446,11 +1476,12 @@ The list of currently supported options that can be reconfigured is this:
|
|||||||
the runtime shipped with the official docker packages.
|
the runtime shipped with the official docker packages.
|
||||||
- `runtimes`: it updates the list of available OCI runtimes that can
|
- `runtimes`: it updates the list of available OCI runtimes that can
|
||||||
be used to run containers.
|
be used to run containers.
|
||||||
- `authorization-plugin`: specifies the authorization plugins to use.
|
- `authorization-plugin`: it specifies the authorization plugins to use.
|
||||||
- `allow-nondistributable-artifacts`: Replaces the set of registries to which the daemon will push nondistributable artifacts with a new set of registries.
|
- `allow-nondistributable-artifacts`: Replaces the set of registries to which the daemon will push nondistributable artifacts with a new set of registries.
|
||||||
- `insecure-registries`: it replaces the daemon insecure registries with a new set of insecure registries. If some existing insecure registries in daemon's configuration are not in newly reloaded insecure resgitries, these existing ones will be removed from daemon's config.
|
- `insecure-registries`: it replaces the daemon insecure registries with a new set of insecure registries. If some existing insecure registries in daemon's configuration are not in newly reloaded insecure resgitries, these existing ones will be removed from daemon's config.
|
||||||
- `registry-mirrors`: it replaces the daemon registry mirrors with a new set of registry mirrors. If some existing registry mirrors in daemon's configuration are not in newly reloaded registry mirrors, these existing ones will be removed from daemon's config.
|
- `registry-mirrors`: it replaces the daemon registry mirrors with a new set of registry mirrors. If some existing registry mirrors in daemon's configuration are not in newly reloaded registry mirrors, these existing ones will be removed from daemon's config.
|
||||||
- `shutdown-timeout`: it replaces the daemon's existing configuration timeout with a new timeout for shutting down all containers.
|
- `shutdown-timeout`: it replaces the daemon's existing configuration timeout with a new timeout for shutting down all containers.
|
||||||
|
- `features`: it explicitly enables or disables specific features.
|
||||||
|
|
||||||
Updating and reloading the cluster configurations such as `--cluster-store`,
|
Updating and reloading the cluster configurations such as `--cluster-store`,
|
||||||
`--cluster-advertise` and `--cluster-store-opts` will take effect only if
|
`--cluster-advertise` and `--cluster-store-opts` will take effect only if
|
||||||
|
|||||||
@@ -31,7 +31,12 @@ Options:
|
|||||||
## Description
|
## Description
|
||||||
|
|
||||||
Use `docker events` to get real-time events from the server. These events differ
|
Use `docker events` to get real-time events from the server. These events differ
|
||||||
per Docker object type.
|
per Docker object type. Different event types have different scopes. Local
|
||||||
|
scoped events are only seen on the node they take place on, and swarm scoped
|
||||||
|
events are seen on all managers.
|
||||||
|
|
||||||
|
Only the last 1000 log events are returned. You can use filters to further limit
|
||||||
|
the number of events returned.
|
||||||
|
|
||||||
### Object types
|
### Object types
|
||||||
|
|
||||||
@@ -160,6 +165,9 @@ that have elapsed since January 1, 1970 (midnight UTC/GMT), not counting leap
|
|||||||
seconds (aka Unix epoch or Unix time), and the optional .nanoseconds field is a
|
seconds (aka Unix epoch or Unix time), and the optional .nanoseconds field is a
|
||||||
fraction of a second no more than nine digits long.
|
fraction of a second no more than nine digits long.
|
||||||
|
|
||||||
|
Only the last 1000 log events are returned. You can use filters to further limit
|
||||||
|
the number of events returned.
|
||||||
|
|
||||||
#### Filtering
|
#### Filtering
|
||||||
|
|
||||||
The filtering flag (`-f` or `--filter`) format is of "key=value". If you would
|
The filtering flag (`-f` or `--filter`) format is of "key=value". If you would
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ Options:
|
|||||||
-c, --change value Apply Dockerfile instruction to the created image (default [])
|
-c, --change value Apply Dockerfile instruction to the created image (default [])
|
||||||
--help Print usage
|
--help Print usage
|
||||||
-m, --message string Set commit message for imported image
|
-m, --message string Set commit message for imported image
|
||||||
|
--platform string Set platform if server is multi-platform capable
|
||||||
```
|
```
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
@@ -87,3 +88,11 @@ Note the `sudo` in this example – you must preserve
|
|||||||
the ownership of the files (especially root ownership) during the
|
the ownership of the files (especially root ownership) during the
|
||||||
archiving with tar. If you are not root (or the sudo command) when you
|
archiving with tar. If you are not root (or the sudo command) when you
|
||||||
tar, then the ownerships might not get preserved.
|
tar, then the ownerships might not get preserved.
|
||||||
|
|
||||||
|
## When the daemon supports multiple operating systems
|
||||||
|
If the daemon supports multiple operating systems, and the image being imported
|
||||||
|
does not match the default operating system, it may be necessary to add
|
||||||
|
`--platform`. This would be necessary when importing a Linux image into a Windows
|
||||||
|
daemon.
|
||||||
|
|
||||||
|
# docker import --platform=linux .\linuximage.tar
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ you can download them from:
|
|||||||
- Microsoft Windows Credential Manager: https://github.com/docker/docker-credential-helpers/releases
|
- Microsoft Windows Credential Manager: https://github.com/docker/docker-credential-helpers/releases
|
||||||
- [pass](https://www.passwordstore.org/): https://github.com/docker/docker-credential-helpers/releases
|
- [pass](https://www.passwordstore.org/): https://github.com/docker/docker-credential-helpers/releases
|
||||||
|
|
||||||
|
#### Configure the credentials store
|
||||||
|
|
||||||
You need to specify the credentials store in `$HOME/.docker/config.json`
|
You need to specify the credentials store in `$HOME/.docker/config.json`
|
||||||
to tell the docker engine to use it. The value of the config property should be
|
to tell the docker engine to use it. The value of the config property should be
|
||||||
the suffix of the program to use (i.e. everything after `docker-credential-`).
|
the suffix of the program to use (i.e. everything after `docker-credential-`).
|
||||||
@@ -99,7 +101,7 @@ For example, to use `docker-credential-osxkeychain`:
|
|||||||
If you are currently logged in, run `docker logout` to remove
|
If you are currently logged in, run `docker logout` to remove
|
||||||
the credentials from the file and run `docker login` again.
|
the credentials from the file and run `docker login` again.
|
||||||
|
|
||||||
### Default behavior
|
#### Default behavior
|
||||||
|
|
||||||
By default, Docker looks for the native binary on each of the platforms, i.e.
|
By default, Docker looks for the native binary on each of the platforms, i.e.
|
||||||
"osxkeychain" on macOS, "wincred" on windows, and "pass" on Linux. A special
|
"osxkeychain" on macOS, "wincred" on windows, and "pass" on Linux. A special
|
||||||
@@ -108,7 +110,7 @@ it cannot find the "pass" binary. If none of these binaries are present, it
|
|||||||
stores the credentials (i.e. password) in base64 encoding in the config files
|
stores the credentials (i.e. password) in base64 encoding in the config files
|
||||||
described above.
|
described above.
|
||||||
|
|
||||||
### Credential helper protocol
|
#### Credential helper protocol
|
||||||
|
|
||||||
Credential helpers can be any program or script that follows a very simple protocol.
|
Credential helpers can be any program or script that follows a very simple protocol.
|
||||||
This protocol is heavily inspired by Git, but it differs in the information shared.
|
This protocol is heavily inspired by Git, but it differs in the information shared.
|
||||||
@@ -162,7 +164,7 @@ designated programs to handle credentials for *specific registries*. The default
|
|||||||
credential store (`credsStore` or the config file itself) will not be used for
|
credential store (`credsStore` or the config file itself) will not be used for
|
||||||
operations concerning credentials of the specified registries.
|
operations concerning credentials of the specified registries.
|
||||||
|
|
||||||
### Logging out
|
#### Configure credential helpers
|
||||||
|
|
||||||
If you are currently logged in, run `docker logout` to remove
|
If you are currently logged in, run `docker logout` to remove
|
||||||
the credentials from the default store.
|
the credentials from the default store.
|
||||||
@@ -182,3 +184,7 @@ For example:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Related commands
|
||||||
|
|
||||||
|
* [logout](logout.md)
|
||||||
|
|||||||
@@ -30,3 +30,7 @@ Options:
|
|||||||
```bash
|
```bash
|
||||||
$ docker logout localhost:8080
|
$ docker logout localhost:8080
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Related commands
|
||||||
|
|
||||||
|
* [login](login.md)
|
||||||
|
|||||||
@@ -177,7 +177,7 @@ This is similar to tagging an image and pushing it to a foreign registry.
|
|||||||
|
|
||||||
After you have created your local copy of the manifest list, you may optionally
|
After you have created your local copy of the manifest list, you may optionally
|
||||||
`annotate` it. Annotations allowed are the architecture and operating system (overriding the image's current values),
|
`annotate` it. Annotations allowed are the architecture and operating system (overriding the image's current values),
|
||||||
os features, and an archictecure variant.
|
os features, and an architecture variant.
|
||||||
|
|
||||||
Finally, you need to `push` your manifest list to the desired registry. Below are descriptions of these three commands,
|
Finally, you need to `push` your manifest list to the desired registry. Below are descriptions of these three commands,
|
||||||
and an example putting them all together.
|
and an example putting them all together.
|
||||||
@@ -270,5 +270,5 @@ $ docker manifest create --insecure myprivateregistry.mycompany.com/repo/image:1
|
|||||||
$ docker manifest push --insecure myprivateregistry.mycompany.com/repo/image:tag
|
$ docker manifest push --insecure myprivateregistry.mycompany.com/repo/image:tag
|
||||||
```
|
```
|
||||||
|
|
||||||
Note that the `--insecure` flag is not required to annotate a manifest list, since annotations are to a locally-stored copy of a manifest list. You may also skip the `--insecure` flag if you are performaing a `docker manifest inspect` on a locally-stored manifest list. Be sure to keep in mind that locally-stored manifest lists are never used by the engine on a `docker pull`.
|
Note that the `--insecure` flag is not required to annotate a manifest list, since annotations are to a locally-stored copy of a manifest list. You may also skip the `--insecure` flag if you are performing a `docker manifest inspect` on a locally-stored manifest list. Be sure to keep in mind that locally-stored manifest lists are never used by the engine on a `docker pull`.
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ Valid placeholders for the Go template are listed below:
|
|||||||
|
|
||||||
Placeholder | Description
|
Placeholder | Description
|
||||||
----------------|------------------------------------------------------------------------------------------
|
----------------|------------------------------------------------------------------------------------------
|
||||||
|
`.ID` | Task ID
|
||||||
`.Name` | Task name
|
`.Name` | Task name
|
||||||
`.Image` | Task image
|
`.Image` | Task image
|
||||||
`.Node` | Node ID
|
`.Node` | Node ID
|
||||||
|
|||||||
@@ -26,6 +26,17 @@ Options:
|
|||||||
--no-prune Do not delete untagged parents
|
--no-prune Do not delete untagged parents
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Removes (and un-tags) one or more images from the host node. If an image has
|
||||||
|
multiple tags, using this command with the tag as a parameter only removes the
|
||||||
|
tag. If the tag is the only one for the image, both the image and the tag are
|
||||||
|
removed.
|
||||||
|
|
||||||
|
This does not remove images from a registry. You cannot remove an image of a
|
||||||
|
running container unless you use the `-f` option. To see all images on a host
|
||||||
|
use the [`docker image ls`](images.md) command.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
You can remove an image using its short or long ID, its tag, or its digest. If
|
You can remove an image using its short or long ID, its tag, or its digest. If
|
||||||
@@ -46,11 +57,11 @@ $ docker rmi fd484f19954f
|
|||||||
Error: Conflict, cannot delete image fd484f19954f because it is tagged in multiple repositories, use -f to force
|
Error: Conflict, cannot delete image fd484f19954f because it is tagged in multiple repositories, use -f to force
|
||||||
2013/12/11 05:47:16 Error: failed to remove one or more images
|
2013/12/11 05:47:16 Error: failed to remove one or more images
|
||||||
|
|
||||||
$ docker rmi test1
|
$ docker rmi test1:latest
|
||||||
|
|
||||||
Untagged: test1:latest
|
Untagged: test1:latest
|
||||||
|
|
||||||
$ docker rmi test2
|
$ docker rmi test2:latest
|
||||||
|
|
||||||
Untagged: test2:latest
|
Untagged: test2:latest
|
||||||
|
|
||||||
@@ -60,7 +71,7 @@ $ docker images
|
|||||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||||
test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB)
|
test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB)
|
||||||
|
|
||||||
$ docker rmi test
|
$ docker rmi test:latest
|
||||||
|
|
||||||
Untagged: test:latest
|
Untagged: test:latest
|
||||||
Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8
|
Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8
|
||||||
|
|||||||
@@ -418,7 +418,7 @@ $ docker run -l my-label --label com.example.foo=bar ubuntu bash
|
|||||||
```
|
```
|
||||||
|
|
||||||
The `my-label` key doesn't specify a value so the label defaults to an empty
|
The `my-label` key doesn't specify a value so the label defaults to an empty
|
||||||
string(`""`). To add multiple labels, repeat the label flag (`-l` or `--label`).
|
string (`""`). To add multiple labels, repeat the label flag (`-l` or `--label`).
|
||||||
|
|
||||||
The `key=value` must be unique to avoid overwriting the label value. If you
|
The `key=value` must be unique to avoid overwriting the label value. If you
|
||||||
specify labels with identical keys but different values, each subsequent value
|
specify labels with identical keys but different values, each subsequent value
|
||||||
@@ -717,15 +717,15 @@ $ docker run -d --isolation default busybox top
|
|||||||
On Windows, `--isolation` can take one of these values:
|
On Windows, `--isolation` can take one of these values:
|
||||||
|
|
||||||
|
|
||||||
| Value | Description |
|
| Value | Description |
|
||||||
|:----------|:-------------------------------------------------------------------------------------------|
|
|:----------|:------------------------------------------------------------------------------------------------------------------|
|
||||||
| `default` | Use the value specified by the Docker daemon's `--exec-opt` or system default (see below). |
|
| `default` | Use the value specified by the Docker daemon's `--exec-opt` or system default (see below). |
|
||||||
| `process` | Shared-kernel namespace isolation (not supported on Windows client operating systems). |
|
| `process` | Shared-kernel namespace isolation (not supported on Windows client operating systems older than Windows 10 1809). |
|
||||||
| `hyperv` | Hyper-V hypervisor partition-based isolation. |
|
| `hyperv` | Hyper-V hypervisor partition-based isolation. |
|
||||||
|
|
||||||
The default isolation on Windows server operating systems is `process`. The default (and only supported)
|
The default isolation on Windows server operating systems is `process`. The default
|
||||||
isolation on Windows client operating systems is `hyperv`. An attempt to start a container on a client
|
isolation on Windows client operating systems is `hyperv`. An attempt to start a container on a client
|
||||||
operating system with `--isolation process` will fail.
|
operating system older than Windows 10 1809 with `--isolation process` will fail.
|
||||||
|
|
||||||
On Windows server, assuming the default configuration, these commands are equivalent
|
On Windows server, assuming the default configuration, these commands are equivalent
|
||||||
and result in `process` isolation:
|
and result in `process` isolation:
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ tutorial](https://docs.docker.com/engine/swarm/swarm-tutorial/rolling-update/).
|
|||||||
|
|
||||||
### Set environment variables (-e, --env)
|
### Set environment variables (-e, --env)
|
||||||
|
|
||||||
This sets an environmental variable for all tasks in a service. For example:
|
This sets an environment variable for all tasks in a service. For example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ docker service create \
|
$ docker service create \
|
||||||
|
|||||||
@@ -171,5 +171,5 @@ On Windows:
|
|||||||
"table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
|
"table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
|
||||||
|
|
||||||
|
|
||||||
> **Note**: On Docker 17.09 and older, the `{{.Container}}` column was used, in
|
> **Note**: On Docker 17.09 and older, the `{{.Container}}` column was used,
|
||||||
> stead of `{{.ID}}\t{{.Name}}`.
|
> instead of `{{.ID}}\t{{.Name}}`.
|
||||||
|
|||||||
@@ -1085,7 +1085,7 @@ per second from `/dev/sda`:
|
|||||||
|
|
||||||
$ docker run -it --device-read-bps /dev/sda:1mb ubuntu
|
$ docker run -it --device-read-bps /dev/sda:1mb ubuntu
|
||||||
|
|
||||||
The `--device-write-bps` flag limits the write rate (bytes per second)to a device.
|
The `--device-write-bps` flag limits the write rate (bytes per second) to a device.
|
||||||
For example, this command creates a container and limits the write rate to `1mb`
|
For example, this command creates a container and limits the write rate to `1mb`
|
||||||
per second for `/dev/sda`:
|
per second for `/dev/sda`:
|
||||||
|
|
||||||
@@ -1555,7 +1555,7 @@ The example below mounts an empty tmpfs into the container with the `rw`,
|
|||||||
If neither 'rw' or 'ro' is specified then the volume is mounted in
|
If neither 'rw' or 'ro' is specified then the volume is mounted in
|
||||||
read-write mode.
|
read-write mode.
|
||||||
|
|
||||||
The `nocopy` modes is used to disable automatic copying requested volume
|
The `nocopy` mode is used to disable automatically copying the requested volume
|
||||||
path in the container to the volume storage location.
|
path in the container to the volume storage location.
|
||||||
For named volumes, `copy` is the default mode. Copy modes are not supported
|
For named volumes, `copy` is the default mode. Copy modes are not supported
|
||||||
for bind-mounted volumes.
|
for bind-mounted volumes.
|
||||||
|
|||||||
+19
-1
@@ -19,9 +19,10 @@ const descriptionSourcePath = "docs/reference/commandline/"
|
|||||||
|
|
||||||
func generateCliYaml(opts *options) error {
|
func generateCliYaml(opts *options) error {
|
||||||
stdin, stdout, stderr := term.StdStreams()
|
stdin, stdout, stderr := term.StdStreams()
|
||||||
dockerCli := command.NewDockerCli(stdin, stdout, stderr, false)
|
dockerCli := command.NewDockerCli(stdin, stdout, stderr, false, nil)
|
||||||
cmd := &cobra.Command{Use: "docker"}
|
cmd := &cobra.Command{Use: "docker"}
|
||||||
commands.AddCommands(cmd, dockerCli)
|
commands.AddCommands(cmd, dockerCli)
|
||||||
|
disableFlagsInUseLine(cmd)
|
||||||
source := filepath.Join(opts.source, descriptionSourcePath)
|
source := filepath.Join(opts.source, descriptionSourcePath)
|
||||||
if err := loadLongDescription(cmd, source); err != nil {
|
if err := loadLongDescription(cmd, source); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -31,6 +32,23 @@ func generateCliYaml(opts *options) error {
|
|||||||
return GenYamlTree(cmd, opts.target)
|
return GenYamlTree(cmd, opts.target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func disableFlagsInUseLine(cmd *cobra.Command) {
|
||||||
|
visitAll(cmd, func(ccmd *cobra.Command) {
|
||||||
|
// do not add a `[flags]` to the end of the usage line.
|
||||||
|
ccmd.DisableFlagsInUseLine = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// visitAll will traverse all commands from the root.
|
||||||
|
// This is different from the VisitAll of cobra.Command where only parents
|
||||||
|
// are checked.
|
||||||
|
func visitAll(root *cobra.Command, fn func(*cobra.Command)) {
|
||||||
|
for _, cmd := range root.Commands() {
|
||||||
|
visitAll(cmd, fn)
|
||||||
|
}
|
||||||
|
fn(root)
|
||||||
|
}
|
||||||
|
|
||||||
func loadLongDescription(cmd *cobra.Command, path ...string) error {
|
func loadLongDescription(cmd *cobra.Command, path ...string) error {
|
||||||
for _, cmd := range cmd.Commands() {
|
for _, cmd := range cmd.Commands() {
|
||||||
if cmd.Name() == "" {
|
if cmd.Name() == "" {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user