mirror of
https://github.com/docker/cli.git
synced 2026-08-27 18:14:20 -05:00
Compare commits
78
Commits
v26.0.0-rc1
...
26.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3c863ff8d3 | ||
|
|
c1b7df309a | ||
|
|
d260a54c81 | ||
|
|
3369ffe3e5 | ||
|
|
3cf84fbc5b | ||
|
|
b1b03b3caa | ||
|
|
57d2fbb70e | ||
|
|
c33cc928bb | ||
|
|
156e20ca86 | ||
|
|
7522a62d26 | ||
|
|
073e4e850e | ||
|
|
6d46ff7438 | ||
|
|
1d214b009d | ||
|
|
3092f67d5d | ||
|
|
f01c09045d | ||
|
|
2ae903e86c | ||
|
|
5931a2f592 | ||
|
|
ed9dd75575 | ||
|
|
69575f6175 | ||
|
|
fa84cfd802 | ||
|
|
b70a26deaf | ||
|
|
23563728bc | ||
|
|
ea3201c575 | ||
|
|
c050bf0909 | ||
|
|
4eef4afbf4 | ||
|
|
396a0823f8 | ||
|
|
f96d8e78c4 | ||
|
|
b120b96ac7 | ||
|
|
24186d8008 | ||
|
|
48b5efee03 | ||
|
|
38c3ff67aa | ||
|
|
f8fc5b6bc3 | ||
|
|
a4a79d75c0 | ||
|
|
115c8d56e5 | ||
|
|
38fcd1ca63 | ||
|
|
9392831817 | ||
|
|
4e9abfecf5 | ||
|
|
dc4163fb1a | ||
|
|
8adf1ddb86 | ||
|
|
c8e470057a | ||
|
|
3da26a5e79 | ||
|
|
4bb2abaa54 | ||
|
|
90c33dbfd9 | ||
|
|
d17b3b2d80 | ||
|
|
9349f58b8a | ||
|
|
adb018084c | ||
|
|
a2f3f40233 | ||
|
|
46afd26c45 | ||
|
|
d06f137170 | ||
|
|
b55cf2f71f | ||
|
|
412d6fca9c | ||
|
|
5c54f75f2a | ||
|
|
617377c045 | ||
|
|
860b4f3a7d | ||
|
|
645b973521 | ||
|
|
3cf2fe0fff | ||
|
|
952c807716 | ||
|
|
a8379092af | ||
|
|
a1361a1372 | ||
|
|
ab9d560570 | ||
|
|
ba6460829b | ||
|
|
3b77477943 | ||
|
|
181575bf55 | ||
|
|
10bf91a02d | ||
|
|
310daf2db9 | ||
|
|
238d659ff9 | ||
|
|
4a43b8eaed | ||
|
|
b086d72769 | ||
|
|
35e6a41ff2 | ||
|
|
5e80232398 | ||
|
|
19d02cd101 | ||
|
|
ec35bab4fa | ||
|
|
d51ba41791 | ||
|
|
a5b82e9f87 | ||
|
|
abe78b79de | ||
|
|
9015b71163 | ||
|
|
5786f20687 | ||
|
|
85dcacd78f |
@@ -26,6 +26,8 @@ jobs:
|
||||
codeql:
|
||||
runs-on: 'ubuntu-latest'
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
DISABLE_WARN_OUTSIDE_CONTAINER: '1'
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
@@ -52,6 +54,16 @@ jobs:
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
# CodeQL 2.16.4's auto-build added support for multi-module repositories,
|
||||
# and is trying to be smart by searching for modules in every directory,
|
||||
# including vendor directories. If no module is found, it's creating one
|
||||
# which is ... not what we want, so let's give it a "go.mod".
|
||||
# see: https://github.com/docker/cli/pull/4944#issuecomment-2002034698
|
||||
-
|
||||
name: Create go.mod
|
||||
run: |
|
||||
ln -s vendor.mod go.mod
|
||||
ln -s vendor.sum go.sum
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.21.7
|
||||
go-version: 1.21.9
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
|
||||
+9
-3
@@ -4,8 +4,8 @@ ARG BASE_VARIANT=alpine
|
||||
ARG ALPINE_VERSION=3.18
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.21.7
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG GO_VERSION=1.21.9
|
||||
ARG XX_VERSION=1.4.0
|
||||
ARG GOVERSIONINFO_VERSION=v1.3.0
|
||||
ARG GOTESTSUM_VERSION=v1.10.0
|
||||
ARG BUILDX_VERSION=0.12.1
|
||||
@@ -123,8 +123,14 @@ COPY --link . .
|
||||
FROM scratch AS plugins
|
||||
COPY --from=build-plugins /out .
|
||||
|
||||
FROM scratch AS bin-image
|
||||
FROM scratch AS bin-image-linux
|
||||
COPY --from=build /out/docker /docker
|
||||
FROM scratch AS bin-image-darwin
|
||||
COPY --from=build /out/docker /docker
|
||||
FROM scratch AS bin-image-windows
|
||||
COPY --from=build /out/docker /docker.exe
|
||||
|
||||
FROM bin-image-${TARGETOS} AS bin-image
|
||||
|
||||
FROM scratch AS binary
|
||||
COPY --from=build /out .
|
||||
|
||||
@@ -52,7 +52,7 @@ shellcheck: ## run shellcheck validation
|
||||
.PHONY: fmt
|
||||
fmt: ## run gofumpt (if present) or gofmt
|
||||
@if command -v gofumpt > /dev/null; then \
|
||||
gofumpt -w -d -lang=1.19 . ; \
|
||||
gofumpt -w -d -lang=1.21 . ; \
|
||||
else \
|
||||
go list -f {{.Dir}} ./... | xargs gofmt -w -s -d ; \
|
||||
fi
|
||||
|
||||
@@ -2,11 +2,14 @@ package manager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -30,6 +33,10 @@ const (
|
||||
// is, one which failed it's candidate test) and contains the
|
||||
// reason for the failure.
|
||||
CommandAnnotationPluginInvalid = "com.docker.cli.plugin-invalid"
|
||||
|
||||
// CommandAnnotationPluginCommandPath is added to overwrite the
|
||||
// command path for a plugin invocation.
|
||||
CommandAnnotationPluginCommandPath = "com.docker.cli.plugin.command_path"
|
||||
)
|
||||
|
||||
var pluginCommandStubsOnce sync.Once
|
||||
@@ -98,3 +105,44 @@ func AddPluginCommandStubs(dockerCli command.Cli, rootCmd *cobra.Command) (err e
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
const (
|
||||
dockerCliAttributePrefix = attribute.Key("docker.cli")
|
||||
|
||||
cobraCommandPath = attribute.Key("cobra.command_path")
|
||||
)
|
||||
|
||||
func getPluginResourceAttributes(cmd *cobra.Command, plugin Plugin) attribute.Set {
|
||||
commandPath := cmd.Annotations[CommandAnnotationPluginCommandPath]
|
||||
if commandPath == "" {
|
||||
commandPath = fmt.Sprintf("%s %s", cmd.CommandPath(), plugin.Name)
|
||||
}
|
||||
|
||||
attrSet := attribute.NewSet(
|
||||
cobraCommandPath.String(commandPath),
|
||||
)
|
||||
|
||||
kvs := make([]attribute.KeyValue, 0, attrSet.Len())
|
||||
for iter := attrSet.Iter(); iter.Next(); {
|
||||
attr := iter.Attribute()
|
||||
kvs = append(kvs, attribute.KeyValue{
|
||||
Key: dockerCliAttributePrefix + "." + attr.Key,
|
||||
Value: attr.Value,
|
||||
})
|
||||
}
|
||||
return attribute.NewSet(kvs...)
|
||||
}
|
||||
|
||||
func appendPluginResourceAttributesEnvvar(env []string, cmd *cobra.Command, plugin Plugin) []string {
|
||||
if attrs := getPluginResourceAttributes(cmd, plugin); attrs.Len() > 0 {
|
||||
// values in environment variables need to be in baggage format
|
||||
// otel/baggage package can be used after update to v1.22, currently it encodes incorrectly
|
||||
attrsSlice := make([]string, attrs.Len())
|
||||
for iter := attrs.Iter(); iter.Next(); {
|
||||
i, v := iter.IndexedAttribute()
|
||||
attrsSlice[i] = string(v.Key) + "=" + url.PathEscape(v.Value.AsString())
|
||||
}
|
||||
env = append(env, ResourceAttributesEnvvar+"="+strings.Join(attrsSlice, ","))
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
@@ -17,11 +17,17 @@ import (
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
// ReexecEnvvar is the name of an ennvar which is set to the command
|
||||
// used to originally invoke the docker CLI when executing a
|
||||
// plugin. Assuming $PATH and $CWD remain unchanged this should allow
|
||||
// the plugin to re-execute the original CLI.
|
||||
const ReexecEnvvar = "DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND"
|
||||
const (
|
||||
// ReexecEnvvar is the name of an ennvar which is set to the command
|
||||
// used to originally invoke the docker CLI when executing a
|
||||
// plugin. Assuming $PATH and $CWD remain unchanged this should allow
|
||||
// the plugin to re-execute the original CLI.
|
||||
ReexecEnvvar = "DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND"
|
||||
|
||||
// ResourceAttributesEnvvar is the name of the envvar that includes additional
|
||||
// resource attributes for OTEL.
|
||||
ResourceAttributesEnvvar = "OTEL_RESOURCE_ATTRIBUTES"
|
||||
)
|
||||
|
||||
// errPluginNotFound is the error returned when a plugin could not be found.
|
||||
type errPluginNotFound string
|
||||
@@ -236,6 +242,7 @@ func PluginRunCommand(dockerCli command.Cli, name string, rootcmd *cobra.Command
|
||||
|
||||
cmd.Env = os.Environ()
|
||||
cmd.Env = append(cmd.Env, ReexecEnvvar+"="+os.Args[0])
|
||||
cmd.Env = appendPluginResourceAttributesEnvvar(cmd.Env, rootcmd, plugin)
|
||||
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
+2
-2
@@ -470,7 +470,7 @@ Common Commands:
|
||||
Management Commands:
|
||||
|
||||
{{- range managementSubCommands . }}
|
||||
{{rpad (decoratedName .) (add .NamePadding 1)}}{{.Short}}{{ if isPlugin .}} {{vendorAndVersion .}}{{ end}}
|
||||
{{rpad (decoratedName .) (add .NamePadding 1)}}{{.Short}}
|
||||
{{- end}}
|
||||
|
||||
{{- end}}
|
||||
@@ -479,7 +479,7 @@ Management Commands:
|
||||
Swarm Commands:
|
||||
|
||||
{{- range orchestratorSubCommands . }}
|
||||
{{rpad (decoratedName .) (add .NamePadding 1)}}{{.Short}}{{ if isPlugin .}} {{vendorAndVersion .}}{{ end}}
|
||||
{{rpad (decoratedName .) (add .NamePadding 1)}}{{.Short}}
|
||||
{{- end}}
|
||||
|
||||
{{- end}}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
builderPruneFunc func(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error)
|
||||
}
|
||||
|
||||
func (c *fakeClient) BuildCachePrune(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) {
|
||||
if c.builderPruneFunc != nil {
|
||||
return c.builderPruneFunc(ctx, opts)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -66,8 +66,10 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
|
||||
if options.all {
|
||||
warning = allCacheWarning
|
||||
}
|
||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return 0, "", nil
|
||||
if !options.force {
|
||||
if r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), warning); !r || err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().BuildCachePrune(ctx, types.BuildCachePruneOptions{
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestBuilderPromptTermination(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
builderPruneFunc: func(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) {
|
||||
return nil, errors.New("fakeClient builderPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
cmd := NewPruneCommand(cli)
|
||||
test.TerminatePrompt(ctx, t, cmd, cli, func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
assert.ErrorIs(t, err, command.ErrPromptTerminated)
|
||||
})
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
@@ -35,6 +36,7 @@ type fakeClient struct {
|
||||
containerExecResizeFunc func(id string, options container.ResizeOptions) error
|
||||
containerRemoveFunc func(ctx context.Context, containerID string, options container.RemoveOptions) error
|
||||
containerKillFunc func(ctx context.Context, containerID, signal string) error
|
||||
containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error)
|
||||
Version string
|
||||
}
|
||||
|
||||
@@ -164,3 +166,10 @@ func (f *fakeClient) ContainerKill(ctx context.Context, containerID, signal stri
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error) {
|
||||
if f.containerPruneFunc != nil {
|
||||
return f.containerPruneFunc(ctx, pruneFilters)
|
||||
}
|
||||
return types.ContainersPruneReport{}, nil
|
||||
}
|
||||
|
||||
@@ -53,8 +53,10 @@ Are you sure you want to continue?`
|
||||
func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
|
||||
|
||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return 0, "", nil
|
||||
if !options.force {
|
||||
if r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), warning); !r || err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().ContainersPrune(ctx, pruneFilters)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestContainerPrunePromptTermination(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error) {
|
||||
return types.ContainersPruneReport{}, errors.New("fakeClient containerPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
cmd := NewPruneCommand(cli)
|
||||
test.TerminatePrompt(ctx, t, cmd, cli, func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
assert.ErrorIs(t, err, command.ErrPromptTerminated)
|
||||
})
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -106,15 +107,6 @@ var acceptedStatsFilters = map[string]bool{
|
||||
func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions) error {
|
||||
apiClient := dockerCLI.Client()
|
||||
|
||||
// Get the daemonOSType if not set already
|
||||
if daemonOSType == "" {
|
||||
sv, err := apiClient.ServerVersion(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
daemonOSType = sv.Os
|
||||
}
|
||||
|
||||
// waitFirst is a WaitGroup to wait first stat data's reach for each container
|
||||
waitFirst := &sync.WaitGroup{}
|
||||
// closeChan is a non-buffered channel used to collect errors from goroutines.
|
||||
@@ -138,9 +130,9 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
return err
|
||||
}
|
||||
|
||||
eh := command.InitEventHandler()
|
||||
eh := newEventHandler()
|
||||
if options.All {
|
||||
eh.Handle(events.ActionCreate, func(e events.Message) {
|
||||
eh.setHandler(events.ActionCreate, func(e events.Message) {
|
||||
s := NewStats(e.Actor.ID[:12])
|
||||
if cStats.add(s) {
|
||||
waitFirst.Add(1)
|
||||
@@ -149,7 +141,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
})
|
||||
}
|
||||
|
||||
eh.Handle(events.ActionStart, func(e events.Message) {
|
||||
eh.setHandler(events.ActionStart, func(e events.Message) {
|
||||
s := NewStats(e.Actor.ID[:12])
|
||||
if cStats.add(s) {
|
||||
waitFirst.Add(1)
|
||||
@@ -158,7 +150,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
})
|
||||
|
||||
if !options.All {
|
||||
eh.Handle(events.ActionDie, func(e events.Message) {
|
||||
eh.setHandler(events.ActionDie, func(e events.Message) {
|
||||
cStats.remove(e.Actor.ID[:12])
|
||||
})
|
||||
}
|
||||
@@ -195,7 +187,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
}
|
||||
|
||||
eventChan := make(chan events.Message)
|
||||
go eh.Watch(eventChan)
|
||||
go eh.watch(eventChan)
|
||||
stopped := make(chan struct{})
|
||||
go monitorContainerEvents(started, eventChan, stopped)
|
||||
defer close(stopped)
|
||||
@@ -267,6 +259,12 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
format = formatter.TableFormatKey
|
||||
}
|
||||
}
|
||||
if daemonOSType == "" {
|
||||
// Get the daemonOSType if not set already. The daemonOSType variable
|
||||
// should already be set when collecting stats as part of "collect()",
|
||||
// so we unlikely hit this code in practice.
|
||||
daemonOSType = dockerCLI.ServerInfo().OSType
|
||||
}
|
||||
statsCtx := formatter.Context{
|
||||
Output: dockerCLI.Out(),
|
||||
Format: NewStatsFormat(format, daemonOSType),
|
||||
@@ -316,3 +314,31 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// newEventHandler initializes and returns an eventHandler
|
||||
func newEventHandler() *eventHandler {
|
||||
return &eventHandler{handlers: make(map[events.Action]func(events.Message))}
|
||||
}
|
||||
|
||||
// eventHandler allows for registering specific events to setHandler.
|
||||
type eventHandler struct {
|
||||
handlers map[events.Action]func(events.Message)
|
||||
}
|
||||
|
||||
func (eh *eventHandler) setHandler(action events.Action, handler func(events.Message)) {
|
||||
eh.handlers[action] = handler
|
||||
}
|
||||
|
||||
// watch ranges over the passed in event chan and processes the events based on the
|
||||
// handlers created for a given action.
|
||||
// To stop watching, close the event chan.
|
||||
func (eh *eventHandler) watch(c <-chan events.Message) {
|
||||
for e := range c {
|
||||
h, exists := eh.handlers[e.Action]
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
logrus.Debugf("event handler: received event: %v", e)
|
||||
go h(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,16 @@ import (
|
||||
|
||||
// EventHandler is abstract interface for user to customize
|
||||
// own handle functions of each type of events
|
||||
//
|
||||
// Deprecated: EventHandler is no longer used, and will be removed in the next release.
|
||||
type EventHandler interface {
|
||||
Handle(action events.Action, h func(events.Message))
|
||||
Watch(c <-chan events.Message)
|
||||
}
|
||||
|
||||
// InitEventHandler initializes and returns an EventHandler
|
||||
//
|
||||
// Deprecated: InitEventHandler is no longer used, and will be removed in the next release.
|
||||
func InitEventHandler() EventHandler {
|
||||
return &eventHandler{handlers: make(map[events.Action]func(events.Message))}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,6 @@ func TestGetContextFromReaderString(t *testing.T) {
|
||||
tarReader := tar.NewReader(tarArchive)
|
||||
|
||||
_, err = tarReader.Next()
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("Error when reading tar archive: %s", err)
|
||||
}
|
||||
|
||||
@@ -67,8 +67,10 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
|
||||
if options.all {
|
||||
warning = allImageWarning
|
||||
}
|
||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return 0, "", nil
|
||||
if !options.force {
|
||||
if r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), warning); !r || err != nil {
|
||||
return 0, "", err
|
||||
}
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().ImagesPrune(ctx, pruneFilters)
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@@ -101,3 +103,19 @@ func TestNewPruneCommandSuccess(t *testing.T) {
|
||||
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("prune-command-success.%s.golden", tc.name))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrunePromptTermination(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagesPruneFunc: func(pruneFilter filters.Args) (types.ImagesPruneReport, error) {
|
||||
return types.ImagesPruneReport{}, errors.New("fakeClient imagesPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
cmd := NewPruneCommand(cli)
|
||||
test.TerminatePrompt(ctx, t, cmd, cli, func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
assert.ErrorIs(t, err, command.ErrPromptTerminated)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
"RepoDigests": null,
|
||||
"Parent": "",
|
||||
"Comment": "",
|
||||
"Created": "",
|
||||
"Container": "",
|
||||
"ContainerConfig": null,
|
||||
"DockerVersion": "",
|
||||
"Author": "",
|
||||
"Config": null,
|
||||
@@ -29,9 +26,6 @@
|
||||
"RepoDigests": null,
|
||||
"Parent": "",
|
||||
"Comment": "",
|
||||
"Created": "",
|
||||
"Container": "",
|
||||
"ContainerConfig": null,
|
||||
"DockerVersion": "",
|
||||
"Author": "",
|
||||
"Config": null,
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
"RepoDigests": null,
|
||||
"Parent": "",
|
||||
"Comment": "",
|
||||
"Created": "",
|
||||
"Container": "",
|
||||
"ContainerConfig": null,
|
||||
"DockerVersion": "",
|
||||
"Author": "",
|
||||
"Config": null,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
@@ -15,6 +16,8 @@ type fakeClient struct {
|
||||
networkDisconnectFunc func(ctx context.Context, networkID, container string, force bool) error
|
||||
networkRemoveFunc func(ctx context.Context, networkID string) error
|
||||
networkListFunc func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
|
||||
networkPruneFunc func(ctx context.Context, pruneFilters filters.Args) (types.NetworksPruneReport, error)
|
||||
networkInspectFunc func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error)
|
||||
}
|
||||
|
||||
func (c *fakeClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
|
||||
@@ -52,6 +55,16 @@ func (c *fakeClient) NetworkRemove(ctx context.Context, networkID string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) NetworkInspectWithRaw(context.Context, string, types.NetworkInspectOptions) (types.NetworkResource, []byte, error) {
|
||||
func (c *fakeClient) NetworkInspectWithRaw(ctx context.Context, networkID string, opts types.NetworkInspectOptions) (types.NetworkResource, []byte, error) {
|
||||
if c.networkInspectFunc != nil {
|
||||
return c.networkInspectFunc(ctx, networkID, opts)
|
||||
}
|
||||
return types.NetworkResource{}, nil, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error) {
|
||||
if c.networkPruneFunc != nil {
|
||||
return c.networkPruneFunc(ctx, pruneFilter)
|
||||
}
|
||||
return types.NetworksPruneReport{}, nil
|
||||
}
|
||||
|
||||
@@ -49,8 +49,10 @@ Are you sure you want to continue?`
|
||||
func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions) (output string, err error) {
|
||||
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
|
||||
|
||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return "", nil
|
||||
if !options.force {
|
||||
if r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), warning); !r || err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().NetworksPrune(ctx, pruneFilters)
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestNetworkPrunePromptTermination(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
networkPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (types.NetworksPruneReport, error) {
|
||||
return types.NetworksPruneReport{}, errors.New("fakeClient networkPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
cmd := NewPruneCommand(cli)
|
||||
test.TerminatePrompt(ctx, t, cmd, cli, func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
assert.ErrorIs(t, err, command.ErrPromptTerminated)
|
||||
})
|
||||
}
|
||||
@@ -46,10 +46,15 @@ func runRemove(ctx context.Context, dockerCli command.Cli, networks []string, op
|
||||
status := 0
|
||||
|
||||
for _, name := range networks {
|
||||
if nw, _, err := client.NetworkInspectWithRaw(ctx, name, types.NetworkInspectOptions{}); err == nil &&
|
||||
nw.Ingress &&
|
||||
!command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), ingressWarning) {
|
||||
continue
|
||||
nw, _, err := client.NetworkInspectWithRaw(ctx, name, types.NetworkInspectOptions{})
|
||||
if err == nil && nw.Ingress {
|
||||
r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), ingressWarning)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !r {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if err := client.NetworkRemove(ctx, name); err != nil {
|
||||
if opts.force && errdefs.IsNotFound(err) {
|
||||
|
||||
@@ -5,7 +5,9 @@ import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
@@ -94,3 +96,27 @@ func TestNetworkRemoveForce(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNetworkRemovePromptTermination(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
networkRemoveFunc: func(ctx context.Context, networkID string) error {
|
||||
return errors.New("fakeClient networkRemoveFunc should not be called")
|
||||
},
|
||||
networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) {
|
||||
return types.NetworkResource{
|
||||
ID: "existing-network",
|
||||
Name: "existing-network",
|
||||
Ingress: true,
|
||||
}, nil, nil
|
||||
},
|
||||
})
|
||||
cmd := newRemoveCommand(cli)
|
||||
cmd.SetArgs([]string{"existing-network"})
|
||||
test.TerminatePrompt(ctx, t, cmd, cli, func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
assert.ErrorIs(t, err, command.ErrPromptTerminated)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ type fakeClient struct {
|
||||
pluginInstallFunc func(name string, options types.PluginInstallOptions) (io.ReadCloser, error)
|
||||
pluginListFunc func(filter filters.Args) (types.PluginsListResponse, error)
|
||||
pluginInspectFunc func(name string) (*types.Plugin, []byte, error)
|
||||
pluginUpgradeFunc func(name string, options types.PluginInstallOptions) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
func (c *fakeClient) PluginCreate(_ context.Context, createContext io.Reader, createOptions types.PluginCreateOptions) error {
|
||||
@@ -75,3 +76,10 @@ func (c *fakeClient) PluginInspectWithRaw(_ context.Context, name string) (*type
|
||||
func (c *fakeClient) Info(context.Context) (system.Info, error) {
|
||||
return system.Info{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeClient) PluginUpgrade(ctx context.Context, name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
||||
if c.pluginUpgradeFunc != nil {
|
||||
return c.pluginUpgradeFunc(name, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -114,7 +114,6 @@ func runCreate(ctx context.Context, dockerCli command.Cli, options pluginCreateO
|
||||
createCtx, err = archive.TarWithOptions(absContextDir, &archive.TarOptions{
|
||||
Compression: compression,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -142,6 +142,7 @@ func acceptPrivileges(dockerCli command.Cli, name string) func(privileges types.
|
||||
for _, privilege := range privileges {
|
||||
fmt.Fprintf(dockerCli.Out(), " - %s: %v\n", privilege.Name, privilege.Value)
|
||||
}
|
||||
return command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), "Do you grant the above permissions?"), nil
|
||||
ctx := context.TODO()
|
||||
return command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), "Do you grant the above permissions?")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Upgrading plugin foo/bar from localhost:5000/foo/bar:v0.1.0 to localhost:5000/foo/bar:v1.0.0
|
||||
Plugin images do not match, are you sure? [y/N]
|
||||
@@ -63,7 +63,10 @@ func runUpgrade(ctx context.Context, dockerCli command.Cli, opts pluginOptions)
|
||||
|
||||
fmt.Fprintf(dockerCli.Out(), "Upgrading plugin %s from %s to %s\n", p.Name, reference.FamiliarString(old), reference.FamiliarString(remote))
|
||||
if !opts.skipRemoteCheck && remote.String() != old.String() {
|
||||
if !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), "Plugin images do not match, are you sure?") {
|
||||
if r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), "Plugin images do not match, are you sure?"); !r || err != nil {
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "canceling upgrade request")
|
||||
}
|
||||
return errors.New("canceling upgrade request")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
func TestUpgradePromptTermination(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginUpgradeFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
||||
return nil, errors.New("should not be called")
|
||||
},
|
||||
pluginInspectFunc: func(name string) (*types.Plugin, []byte, error) {
|
||||
return &types.Plugin{
|
||||
ID: "5724e2c8652da337ab2eedd19fc6fc0ec908e4bd907c7421bf6a8dfc70c4c078",
|
||||
Name: "foo/bar",
|
||||
Enabled: false,
|
||||
PluginReference: "localhost:5000/foo/bar:v0.1.0",
|
||||
}, []byte{}, nil
|
||||
},
|
||||
})
|
||||
cmd := newUpgradeCommand(cli)
|
||||
// need to set a remote address that does not match the plugin
|
||||
// reference sent by the `pluginInspectFunc`
|
||||
cmd.SetArgs([]string{"foo/bar", "localhost:5000/foo/bar:v1.0.0"})
|
||||
test.TerminatePrompt(ctx, t, cmd, cli, func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
assert.ErrorIs(t, err, command.ErrPromptTerminated)
|
||||
})
|
||||
golden.Assert(t, cli.OutBuffer().String(), "plugin-upgrade-terminate.golden")
|
||||
}
|
||||
@@ -137,7 +137,7 @@ func runCreate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
|
||||
return nil
|
||||
}
|
||||
|
||||
return waitOnService(ctx, dockerCli, response.ID, opts.quiet)
|
||||
return WaitOnService(ctx, dockerCli, response.ID, opts.quiet)
|
||||
}
|
||||
|
||||
// setConfigs does double duty: it both sets the ConfigReferences of the
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
)
|
||||
|
||||
// waitOnService waits for the service to converge. It outputs a progress bar,
|
||||
// WaitOnService waits for the service to converge. It outputs a progress bar,
|
||||
// if appropriate based on the CLI flags.
|
||||
func waitOnService(ctx context.Context, dockerCli command.Cli, serviceID string, quiet bool) error {
|
||||
func WaitOnService(ctx context.Context, dockerCli command.Cli, serviceID string, quiet bool) error {
|
||||
errChan := make(chan error, 1)
|
||||
pipeReader, pipeWriter := io.Pipe()
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ func ServiceProgress(ctx context.Context, apiClient client.APIClient, serviceID
|
||||
if converged && time.Since(convergedAt) >= monitor {
|
||||
progressOut.WriteProgress(progress.Progress{
|
||||
ID: "verify",
|
||||
Action: "Service converged",
|
||||
Action: fmt.Sprintf("Service %s converged", serviceID),
|
||||
})
|
||||
if message != nil {
|
||||
progressOut.WriteProgress(*message)
|
||||
|
||||
@@ -62,5 +62,5 @@ func runRollback(ctx context.Context, dockerCli command.Cli, options *serviceOpt
|
||||
return nil
|
||||
}
|
||||
|
||||
return waitOnService(ctx, dockerCli, serviceID, options.quiet)
|
||||
return WaitOnService(ctx, dockerCli, serviceID, options.quiet)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func runScale(ctx context.Context, dockerCli command.Cli, options *scaleOptions,
|
||||
if len(serviceIDs) > 0 {
|
||||
if !options.detach && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.29") {
|
||||
for _, serviceID := range serviceIDs {
|
||||
if err := waitOnService(ctx, dockerCli, serviceID, false); err != nil {
|
||||
if err := WaitOnService(ctx, dockerCli, serviceID, false); err != nil {
|
||||
errs = append(errs, fmt.Sprintf("%s: %v", serviceID, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +249,7 @@ func runUpdate(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet,
|
||||
return nil
|
||||
}
|
||||
|
||||
return waitOnService(ctx, dockerCli, serviceID, options.quiet)
|
||||
return WaitOnService(ctx, dockerCli, serviceID, options.quiet)
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
|
||||
@@ -26,7 +26,7 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return swarm.RunDeploy(cmd.Context(), dockerCli, opts, config)
|
||||
return swarm.RunDeploy(cmd.Context(), dockerCli, cmd.Flags(), &opts, config)
|
||||
},
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return completeNames(dockerCli)(cmd, args, toComplete)
|
||||
@@ -42,5 +42,7 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.StringVar(&opts.ResolveImage, "resolve-image", swarm.ResolveImageAlways,
|
||||
`Query the registry to resolve image digest and supported platforms ("`+swarm.ResolveImageAlways+`", "`+swarm.ResolveImageChanged+`", "`+swarm.ResolveImageNever+`")`)
|
||||
flags.SetAnnotation("resolve-image", "version", []string{"1.30"})
|
||||
flags.BoolVarP(&opts.Detach, "detach", "d", true, "Exit immediately instead of waiting for the stack services to converge")
|
||||
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Suppress progress output")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ type Deploy struct {
|
||||
ResolveImage string
|
||||
SendRegistryAuth bool
|
||||
Prune bool
|
||||
Detach bool
|
||||
Quiet bool
|
||||
}
|
||||
|
||||
// Config holds docker stack config options
|
||||
@@ -36,6 +38,7 @@ type PS struct {
|
||||
// Remove holds docker stack remove options
|
||||
type Remove struct {
|
||||
Namespaces []string
|
||||
Detach bool
|
||||
}
|
||||
|
||||
// Services holds docker stack services options
|
||||
|
||||
@@ -27,5 +27,8 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return completeNames(dockerCli)(cmd, args, toComplete)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.Detach, "detach", "d", true, "Do not wait for stack removal")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -44,3 +44,7 @@ func getStackSecrets(ctx context.Context, apiclient client.APIClient, namespace
|
||||
func getStackConfigs(ctx context.Context, apiclient client.APIClient, namespace string) ([]swarm.Config, error) {
|
||||
return apiclient.ConfigList(ctx, types.ConfigListOptions{Filters: getStackFilter(namespace)})
|
||||
}
|
||||
|
||||
func getStackTasks(ctx context.Context, apiclient client.APIClient, namespace string) ([]swarm.Task, error) {
|
||||
return apiclient.TaskList(ctx, types.TaskListOptions{Filters: getStackFilter(namespace)})
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
// Resolve image constants
|
||||
@@ -22,8 +23,8 @@ const (
|
||||
)
|
||||
|
||||
// RunDeploy is the swarm implementation of docker stack deploy
|
||||
func RunDeploy(ctx context.Context, dockerCli command.Cli, opts options.Deploy, cfg *composetypes.Config) error {
|
||||
if err := validateResolveImageFlag(&opts); err != nil {
|
||||
func RunDeploy(ctx context.Context, dockerCli command.Cli, flags *pflag.FlagSet, opts *options.Deploy, cfg *composetypes.Config) error {
|
||||
if err := validateResolveImageFlag(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
// client side image resolution should not be done when the supported
|
||||
@@ -32,6 +33,11 @@ func RunDeploy(ctx context.Context, dockerCli command.Cli, opts options.Deploy,
|
||||
opts.ResolveImage = ResolveImageNever
|
||||
}
|
||||
|
||||
if opts.Detach && !flags.Changed("detach") {
|
||||
fmt.Fprintln(dockerCli.Err(), "Since --detach=false was not specified, tasks will be created in the background.\n"+
|
||||
"In a future release, --detach=false will become the default.")
|
||||
}
|
||||
|
||||
return deployCompose(ctx, dockerCli, opts, cfg)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,11 @@ package swarm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
servicecli "github.com/docker/cli/cli/command/service"
|
||||
"github.com/docker/cli/cli/command/stack/options"
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
@@ -13,10 +15,9 @@ import (
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
apiclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy, config *composetypes.Config) error {
|
||||
func deployCompose(ctx context.Context, dockerCli command.Cli, opts *options.Deploy, config *composetypes.Config) error {
|
||||
if err := checkDaemonIsSwarmManager(ctx, dockerCli); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -60,7 +61,17 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Depl
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
|
||||
|
||||
serviceIDs, err := deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.Detach {
|
||||
return nil
|
||||
}
|
||||
|
||||
return waitOnServices(ctx, dockerCli, serviceIDs, opts.Quiet)
|
||||
}
|
||||
|
||||
func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} {
|
||||
@@ -87,11 +98,11 @@ func validateExternalNetworks(ctx context.Context, client apiclient.NetworkAPICl
|
||||
network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
|
||||
switch {
|
||||
case errdefs.IsNotFound(err):
|
||||
return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName)
|
||||
return fmt.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName)
|
||||
case err != nil:
|
||||
return err
|
||||
case network.Scope != "swarm":
|
||||
return errors.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, network.Scope)
|
||||
return fmt.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, network.Scope)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -106,13 +117,13 @@ func createSecrets(ctx context.Context, dockerCli command.Cli, secrets []swarm.S
|
||||
case err == nil:
|
||||
// secret already exists, then we update that
|
||||
if err := client.SecretUpdate(ctx, secret.ID, secret.Meta.Version, secretSpec); err != nil {
|
||||
return errors.Wrapf(err, "failed to update secret %s", secretSpec.Name)
|
||||
return fmt.Errorf("failed to update secret %s: %w", secretSpec.Name, err)
|
||||
}
|
||||
case errdefs.IsNotFound(err):
|
||||
// secret does not exist, then we create a new one.
|
||||
fmt.Fprintf(dockerCli.Out(), "Creating secret %s\n", secretSpec.Name)
|
||||
if _, err := client.SecretCreate(ctx, secretSpec); err != nil {
|
||||
return errors.Wrapf(err, "failed to create secret %s", secretSpec.Name)
|
||||
return fmt.Errorf("failed to create secret %s: %w", secretSpec.Name, err)
|
||||
}
|
||||
default:
|
||||
return err
|
||||
@@ -130,13 +141,13 @@ func createConfigs(ctx context.Context, dockerCli command.Cli, configs []swarm.C
|
||||
case err == nil:
|
||||
// config already exists, then we update that
|
||||
if err := client.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil {
|
||||
return errors.Wrapf(err, "failed to update config %s", configSpec.Name)
|
||||
return fmt.Errorf("failed to update config %s: %w", configSpec.Name, err)
|
||||
}
|
||||
case errdefs.IsNotFound(err):
|
||||
// config does not exist, then we create a new one.
|
||||
fmt.Fprintf(dockerCli.Out(), "Creating config %s\n", configSpec.Name)
|
||||
if _, err := client.ConfigCreate(ctx, configSpec); err != nil {
|
||||
return errors.Wrapf(err, "failed to create config %s", configSpec.Name)
|
||||
return fmt.Errorf("failed to create config %s: %w", configSpec.Name, err)
|
||||
}
|
||||
default:
|
||||
return err
|
||||
@@ -169,19 +180,19 @@ func createNetworks(ctx context.Context, dockerCli command.Cli, namespace conver
|
||||
|
||||
fmt.Fprintf(dockerCli.Out(), "Creating network %s\n", name)
|
||||
if _, err := client.NetworkCreate(ctx, name, createOpts); err != nil {
|
||||
return errors.Wrapf(err, "failed to create network %s", name)
|
||||
return fmt.Errorf("failed to create network %s: %w", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func deployServices(ctx context.Context, dockerCli command.Cli, services map[string]swarm.ServiceSpec, namespace convert.Namespace, sendAuth bool, resolveImage string) error {
|
||||
func deployServices(ctx context.Context, dockerCli command.Cli, services map[string]swarm.ServiceSpec, namespace convert.Namespace, sendAuth bool, resolveImage string) ([]string, error) {
|
||||
apiClient := dockerCli.Client()
|
||||
out := dockerCli.Out()
|
||||
|
||||
existingServices, err := getStackServices(ctx, apiClient, namespace.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingServiceMap := make(map[string]swarm.Service)
|
||||
@@ -189,6 +200,8 @@ func deployServices(ctx context.Context, dockerCli command.Cli, services map[str
|
||||
existingServiceMap[service.Spec.Name] = service
|
||||
}
|
||||
|
||||
var serviceIDs []string
|
||||
|
||||
for internalName, serviceSpec := range services {
|
||||
var (
|
||||
name = namespace.Scope(internalName)
|
||||
@@ -200,7 +213,7 @@ func deployServices(ctx context.Context, dockerCli command.Cli, services map[str
|
||||
// Retrieve encoded auth token from the image reference
|
||||
encodedAuth, err = command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), image)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,12 +254,14 @@ func deployServices(ctx context.Context, dockerCli command.Cli, services map[str
|
||||
|
||||
response, err := apiClient.ServiceUpdate(ctx, service.ID, service.Version, serviceSpec, updateOpts)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to update service %s", name)
|
||||
return nil, fmt.Errorf("failed to update service %s: %w", name, err)
|
||||
}
|
||||
|
||||
for _, warning := range response.Warnings {
|
||||
fmt.Fprintln(dockerCli.Err(), warning)
|
||||
}
|
||||
|
||||
serviceIDs = append(serviceIDs, service.ID)
|
||||
} else {
|
||||
fmt.Fprintf(out, "Creating service %s\n", name)
|
||||
|
||||
@@ -257,10 +272,29 @@ func deployServices(ctx context.Context, dockerCli command.Cli, services map[str
|
||||
createOpts.QueryRegistry = true
|
||||
}
|
||||
|
||||
if _, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts); err != nil {
|
||||
return errors.Wrapf(err, "failed to create service %s", name)
|
||||
response, err := apiClient.ServiceCreate(ctx, serviceSpec, createOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create service %s: %w", name, err)
|
||||
}
|
||||
|
||||
serviceIDs = append(serviceIDs, response.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return serviceIDs, nil
|
||||
}
|
||||
|
||||
func waitOnServices(ctx context.Context, dockerCli command.Cli, serviceIDs []string, quiet bool) error {
|
||||
var errs []error
|
||||
for _, serviceID := range serviceIDs {
|
||||
if err := servicecli.WaitOnService(ctx, dockerCli, serviceID, quiet); err != nil {
|
||||
errs = append(errs, fmt.Errorf("%s: %w", serviceID, err))
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -99,7 +99,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
err := deployServices(ctx, client, spec, namespace, false, ResolveImageChanged)
|
||||
_, err := deployServices(ctx, client, spec, namespace, false, ResolveImageChanged)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(receivedOptions.QueryRegistry, tc.expectedQueryRegistry))
|
||||
assert.Check(t, is.Equal(receivedService.TaskTemplate.ContainerSpec.Image, tc.expectedImage))
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
apiclient "github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -58,6 +59,14 @@ func RunRemove(ctx context.Context, dockerCli command.Cli, opts options.Remove)
|
||||
|
||||
if hasError {
|
||||
errs = append(errs, fmt.Sprintf("Failed to remove some resources from stack: %s", namespace))
|
||||
continue
|
||||
}
|
||||
|
||||
if !opts.Detach {
|
||||
err = waitOnTasks(ctx, client, namespace)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Sprintf("Failed to wait on tasks of stack: %s: %s", namespace, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,3 +146,45 @@ func removeConfigs(
|
||||
}
|
||||
return hasError
|
||||
}
|
||||
|
||||
var numberedStates = map[swarm.TaskState]int64{
|
||||
swarm.TaskStateNew: 1,
|
||||
swarm.TaskStateAllocated: 2,
|
||||
swarm.TaskStatePending: 3,
|
||||
swarm.TaskStateAssigned: 4,
|
||||
swarm.TaskStateAccepted: 5,
|
||||
swarm.TaskStatePreparing: 6,
|
||||
swarm.TaskStateReady: 7,
|
||||
swarm.TaskStateStarting: 8,
|
||||
swarm.TaskStateRunning: 9,
|
||||
swarm.TaskStateComplete: 10,
|
||||
swarm.TaskStateShutdown: 11,
|
||||
swarm.TaskStateFailed: 12,
|
||||
swarm.TaskStateRejected: 13,
|
||||
}
|
||||
|
||||
func terminalState(state swarm.TaskState) bool {
|
||||
return numberedStates[state] > numberedStates[swarm.TaskStateRunning]
|
||||
}
|
||||
|
||||
func waitOnTasks(ctx context.Context, client apiclient.APIClient, namespace string) error {
|
||||
terminalStatesReached := 0
|
||||
for {
|
||||
tasks, err := getStackTasks(ctx, client, namespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get tasks: %w", err)
|
||||
}
|
||||
|
||||
for _, task := range tasks {
|
||||
if terminalState(task.Status.State) {
|
||||
terminalStatesReached++
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if terminalStatesReached == len(tasks) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,15 +5,18 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
|
||||
version string
|
||||
serverVersion func(ctx context.Context) (types.Version, error)
|
||||
eventsFn func(context.Context, types.EventsOptions) (<-chan events.Message, <-chan error)
|
||||
version string
|
||||
serverVersion func(ctx context.Context) (types.Version, error)
|
||||
eventsFn func(context.Context, types.EventsOptions) (<-chan events.Message, <-chan error)
|
||||
containerPruneFunc func(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error)
|
||||
networkPruneFunc func(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error)
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
|
||||
@@ -27,3 +30,17 @@ func (cli *fakeClient) ClientVersion() string {
|
||||
func (cli *fakeClient) Events(ctx context.Context, opts types.EventsOptions) (<-chan events.Message, <-chan error) {
|
||||
return cli.eventsFn(ctx, opts)
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ContainersPrune(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error) {
|
||||
if cli.containerPruneFunc != nil {
|
||||
return cli.containerPruneFunc(ctx, pruneFilters)
|
||||
}
|
||||
return types.ContainersPruneReport{}, nil
|
||||
}
|
||||
|
||||
func (cli *fakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error) {
|
||||
if cli.networkPruneFunc != nil {
|
||||
return cli.networkPruneFunc(ctx, pruneFilter)
|
||||
}
|
||||
return types.NetworksPruneReport{}, nil
|
||||
}
|
||||
|
||||
@@ -74,8 +74,10 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
|
||||
if options.pruneVolumes && options.filter.Value().Contains("until") {
|
||||
return fmt.Errorf(`ERROR: The "until" filter is not supported with "--volumes"`)
|
||||
}
|
||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), confirmationMessage(dockerCli, options)) {
|
||||
return nil
|
||||
if !options.force {
|
||||
if r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), confirmationMessage(dockerCli, options)); !r || err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
pruneFuncs := []func(ctx context.Context, dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error){
|
||||
container.RunPrune,
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
@@ -49,3 +54,23 @@ func TestPrunePromptFilters(t *testing.T) {
|
||||
Are you sure you want to continue? [y/N] `
|
||||
assert.Check(t, is.Equal(expected, cli.OutBuffer().String()))
|
||||
}
|
||||
|
||||
func TestSystemPrunePromptTermination(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
containerPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (types.ContainersPruneReport, error) {
|
||||
return types.ContainersPruneReport{}, errors.New("fakeClient containerPruneFunc should not be called")
|
||||
},
|
||||
networkPruneFunc: func(ctx context.Context, pruneFilters filters.Args) (types.NetworksPruneReport, error) {
|
||||
return types.NetworksPruneReport{}, errors.New("fakeClient networkPruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
|
||||
cmd := newPruneCommand(cli)
|
||||
test.TerminatePrompt(ctx, t, cmd, cli, func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
assert.ErrorIs(t, err, command.ErrPromptTerminated)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package trust
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
@@ -44,7 +43,11 @@ func revokeTrust(ctx context.Context, dockerCLI command.Cli, remote string, opti
|
||||
return fmt.Errorf("cannot use a digest reference for IMAGE:TAG")
|
||||
}
|
||||
if imgRefAndAuth.Tag() == "" && !options.forceYes {
|
||||
deleteRemote := command.PromptForConfirmation(os.Stdin, dockerCLI.Out(), fmt.Sprintf("Please confirm you would like to delete all signature data for %s?", remote))
|
||||
deleteRemote, err := command.PromptForConfirmation(ctx, dockerCLI.In(), dockerCLI.Out(), fmt.Sprintf("Please confirm you would like to delete all signature data for %s?", remote))
|
||||
if err != nil {
|
||||
fmt.Fprintf(dockerCLI.Out(), "\nAborting action.\n")
|
||||
return errors.Wrap(err, "aborting action")
|
||||
}
|
||||
if !deleteRemote {
|
||||
fmt.Fprintf(dockerCLI.Out(), "\nAborting action.\n")
|
||||
return nil
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package trust
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/notary"
|
||||
@@ -12,6 +14,7 @@ import (
|
||||
"github.com/theupdateframework/notary/trustpinning"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
"gotest.tools/v3/golden"
|
||||
)
|
||||
|
||||
func TestTrustRevokeCommandErrors(t *testing.T) {
|
||||
@@ -148,3 +151,18 @@ func TestGetSignableRolesForTargetAndRemoveError(t *testing.T) {
|
||||
err = getSignableRolesForTargetAndRemove(target, notaryRepo)
|
||||
assert.Error(t, err, "client is offline")
|
||||
}
|
||||
|
||||
func TestRevokeTrustPromptTermination(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cmd := newRevokeCommand(cli)
|
||||
cmd.SetArgs([]string{"example/trust-demo"})
|
||||
test.TerminatePrompt(ctx, t, cmd, cli, func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
assert.ErrorIs(t, err, command.ErrPromptTerminated)
|
||||
})
|
||||
assert.Equal(t, cli.ErrBuffer().String(), "")
|
||||
golden.Assert(t, cli.OutBuffer().String(), "trust-revoke-prompt-termination.golden")
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package trust
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
@@ -76,6 +75,22 @@ func isLastSignerForReleases(roleWithSig data.Role, allRoles []client.RoleWithSi
|
||||
return counter < releasesRoleWithSigs.Threshold, nil
|
||||
}
|
||||
|
||||
func maybePromptForSignerRemoval(ctx context.Context, dockerCLI command.Cli, repoName, signerName string, isLastSigner, forceYes bool) (bool, error) {
|
||||
if isLastSigner && !forceYes {
|
||||
message := fmt.Sprintf("The signer \"%s\" signed the last released version of %s. "+
|
||||
"Removing this signer will make %s unpullable. "+
|
||||
"Are you sure you want to continue?",
|
||||
signerName, repoName, repoName,
|
||||
)
|
||||
removeSigner, err := command.PromptForConfirmation(ctx, dockerCLI.In(), dockerCLI.Out(), message)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return removeSigner, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// removeSingleSigner attempts to remove a single signer and returns whether signer removal happened.
|
||||
// The signer not being removed doesn't necessarily raise an error e.g. user choosing "No" when prompted for confirmation.
|
||||
func removeSingleSigner(ctx context.Context, dockerCLI command.Cli, repoName, signerName string, forceYes bool) (bool, error) {
|
||||
@@ -110,28 +125,26 @@ func removeSingleSigner(ctx context.Context, dockerCLI command.Cli, repoName, si
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if ok, err := isLastSignerForReleases(role, allRoles); ok && !forceYes {
|
||||
removeSigner := command.PromptForConfirmation(os.Stdin, dockerCLI.Out(), fmt.Sprintf("The signer \"%s\" signed the last released version of %s. "+
|
||||
"Removing this signer will make %s unpullable. "+
|
||||
"Are you sure you want to continue?",
|
||||
signerName, repoName, repoName,
|
||||
))
|
||||
|
||||
if !removeSigner {
|
||||
fmt.Fprintf(dockerCLI.Out(), "\nAborting action.\n")
|
||||
return false, nil
|
||||
}
|
||||
} else if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err = notaryRepo.RemoveDelegationKeys(releasesRoleTUFName, role.KeyIDs); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err = notaryRepo.RemoveDelegationRole(signerDelegation); err != nil {
|
||||
isLastSigner, err := isLastSignerForReleases(role, allRoles)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err = notaryRepo.Publish(); err != nil {
|
||||
ok, err := maybePromptForSignerRemoval(ctx, dockerCLI, repoName, signerName, isLastSigner, forceYes)
|
||||
if err != nil || !ok {
|
||||
fmt.Fprintf(dockerCLI.Out(), "\nAborting action.\n")
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := notaryRepo.RemoveDelegationKeys(releasesRoleTUFName, role.KeyIDs); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := notaryRepo.RemoveDelegationRole(signerDelegation); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if err := notaryRepo.Publish(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
|
||||
@@ -111,7 +111,8 @@ func TestIsLastSignerForReleases(t *testing.T) {
|
||||
releaserole.Name = releasesRoleTUFName
|
||||
releaserole.Threshold = 1
|
||||
allrole := []client.RoleWithSignatures{releaserole}
|
||||
lastsigner, _ := isLastSignerForReleases(role, allrole)
|
||||
lastsigner, err := isLastSignerForReleases(role, allrole)
|
||||
assert.Error(t, err, "all signed tags are currently revoked, use docker trust sign to fix")
|
||||
assert.Check(t, is.Equal(false, lastsigner))
|
||||
|
||||
role.KeyIDs = []string{"deadbeef"}
|
||||
@@ -120,13 +121,15 @@ func TestIsLastSignerForReleases(t *testing.T) {
|
||||
releaserole.Signatures = []data.Signature{sig}
|
||||
releaserole.Threshold = 1
|
||||
allrole = []client.RoleWithSignatures{releaserole}
|
||||
lastsigner, _ = isLastSignerForReleases(role, allrole)
|
||||
lastsigner, err = isLastSignerForReleases(role, allrole)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(true, lastsigner))
|
||||
|
||||
sig.KeyID = "8badf00d"
|
||||
releaserole.Signatures = []data.Signature{sig}
|
||||
releaserole.Threshold = 1
|
||||
allrole = []client.RoleWithSignatures{releaserole}
|
||||
lastsigner, _ = isLastSignerForReleases(role, allrole)
|
||||
lastsigner, err = isLastSignerForReleases(role, allrole)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(false, lastsigner))
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
Please confirm you would like to delete all signature data for example/trust-demo? [y/N]
|
||||
Aborting action.
|
||||
+43
-9
@@ -5,12 +5,15 @@ package command
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
@@ -72,12 +75,21 @@ func PrettyPrint(i any) string {
|
||||
}
|
||||
}
|
||||
|
||||
// PromptForConfirmation requests and checks confirmation from user.
|
||||
// This will display the provided message followed by ' [y/N] '. If
|
||||
// the user input 'y' or 'Y' it returns true other false. If no
|
||||
// message is provided "Are you sure you want to proceed? [y/N] "
|
||||
// will be used instead.
|
||||
func PromptForConfirmation(ins io.Reader, outs io.Writer, message string) bool {
|
||||
type PromptError error
|
||||
|
||||
var ErrPromptTerminated = PromptError(errors.New("prompt terminated"))
|
||||
|
||||
// PromptForConfirmation requests and checks confirmation from the user.
|
||||
// This will display the provided message followed by ' [y/N] '. If the user
|
||||
// input 'y' or 'Y' it returns true otherwise false. If no message is provided,
|
||||
// "Are you sure you want to proceed? [y/N] " will be used instead.
|
||||
//
|
||||
// If the user terminates the CLI with SIGINT or SIGTERM while the prompt is
|
||||
// active, the prompt will return false with an ErrPromptTerminated error.
|
||||
// When the prompt returns an error, the caller should propagate the error up
|
||||
// the stack and close the io.Reader used for the prompt which will prevent the
|
||||
// background goroutine from blocking indefinitely.
|
||||
func PromptForConfirmation(ctx context.Context, ins io.Reader, outs io.Writer, message string) (bool, error) {
|
||||
if message == "" {
|
||||
message = "Are you sure you want to proceed?"
|
||||
}
|
||||
@@ -90,9 +102,31 @@ func PromptForConfirmation(ins io.Reader, outs io.Writer, message string) bool {
|
||||
ins = streams.NewIn(os.Stdin)
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(ins)
|
||||
answer, _, _ := reader.ReadLine()
|
||||
return strings.ToLower(string(answer)) == "y"
|
||||
result := make(chan bool)
|
||||
|
||||
// Catch the termination signal and exit the prompt gracefully.
|
||||
// The caller is responsible for properly handling the termination.
|
||||
notifyCtx, notifyCancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
|
||||
defer notifyCancel()
|
||||
|
||||
go func() {
|
||||
var res bool
|
||||
scanner := bufio.NewScanner(ins)
|
||||
if scanner.Scan() {
|
||||
answer := strings.TrimSpace(scanner.Text())
|
||||
if strings.EqualFold(answer, "y") {
|
||||
res = true
|
||||
}
|
||||
}
|
||||
result <- res
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-notifyCtx.Done():
|
||||
return false, ErrPromptTerminated
|
||||
case r := <-result:
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
// PruneFilters returns consolidated prune filters obtained from config.json and cli
|
||||
|
||||
+192
-8
@@ -1,36 +1,46 @@
|
||||
package command
|
||||
package command_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/pkg/errors"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestStringSliceReplaceAt(t *testing.T) {
|
||||
out, ok := StringSliceReplaceAt([]string{"abc", "foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, -1)
|
||||
out, ok := command.StringSliceReplaceAt([]string{"abc", "foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, -1)
|
||||
assert.Assert(t, ok)
|
||||
assert.DeepEqual(t, []string{"abc", "baz", "bax"}, out)
|
||||
|
||||
out, ok = StringSliceReplaceAt([]string{"foo"}, []string{"foo", "bar"}, []string{"baz"}, -1)
|
||||
out, ok = command.StringSliceReplaceAt([]string{"foo"}, []string{"foo", "bar"}, []string{"baz"}, -1)
|
||||
assert.Assert(t, !ok)
|
||||
assert.DeepEqual(t, []string{"foo"}, out)
|
||||
|
||||
out, ok = StringSliceReplaceAt([]string{"abc", "foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, 0)
|
||||
out, ok = command.StringSliceReplaceAt([]string{"abc", "foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, 0)
|
||||
assert.Assert(t, !ok)
|
||||
assert.DeepEqual(t, []string{"abc", "foo", "bar", "bax"}, out)
|
||||
|
||||
out, ok = StringSliceReplaceAt([]string{"foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, 0)
|
||||
out, ok = command.StringSliceReplaceAt([]string{"foo", "bar", "bax"}, []string{"foo", "bar"}, []string{"baz"}, 0)
|
||||
assert.Assert(t, ok)
|
||||
assert.DeepEqual(t, []string{"baz", "bax"}, out)
|
||||
|
||||
out, ok = StringSliceReplaceAt([]string{"abc", "foo", "bar", "baz"}, []string{"foo", "bar"}, nil, -1)
|
||||
out, ok = command.StringSliceReplaceAt([]string{"abc", "foo", "bar", "baz"}, []string{"foo", "bar"}, nil, -1)
|
||||
assert.Assert(t, ok)
|
||||
assert.DeepEqual(t, []string{"abc", "baz"}, out)
|
||||
|
||||
out, ok = StringSliceReplaceAt([]string{"foo"}, nil, []string{"baz"}, -1)
|
||||
out, ok = command.StringSliceReplaceAt([]string{"foo"}, nil, []string{"baz"}, -1)
|
||||
assert.Assert(t, !ok)
|
||||
assert.DeepEqual(t, []string{"foo"}, out)
|
||||
}
|
||||
@@ -59,7 +69,7 @@ func TestValidateOutputPath(t *testing.T) {
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.path, func(t *testing.T) {
|
||||
err := ValidateOutputPath(testcase.path)
|
||||
err := command.ValidateOutputPath(testcase.path)
|
||||
if testcase.err == nil {
|
||||
assert.NilError(t, err)
|
||||
} else {
|
||||
@@ -68,3 +78,177 @@ func TestValidateOutputPath(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPromptForConfirmation(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
type promptResult struct {
|
||||
result bool
|
||||
err error
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
bufioWriter := bufio.NewWriter(buf)
|
||||
|
||||
var (
|
||||
promptWriter *io.PipeWriter
|
||||
promptReader *io.PipeReader
|
||||
)
|
||||
|
||||
defer func() {
|
||||
if promptWriter != nil {
|
||||
promptWriter.Close()
|
||||
}
|
||||
if promptReader != nil {
|
||||
promptReader.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
for _, tc := range []struct {
|
||||
desc string
|
||||
f func(*testing.T, context.Context, chan promptResult)
|
||||
}{
|
||||
{"SIGINT", func(t *testing.T, ctx context.Context, c chan promptResult) {
|
||||
t.Helper()
|
||||
|
||||
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("PromptForConfirmation did not return after SIGINT")
|
||||
case r := <-c:
|
||||
assert.Check(t, !r.result)
|
||||
assert.ErrorContains(t, r.err, "prompt terminated")
|
||||
}
|
||||
}},
|
||||
{"no", func(t *testing.T, ctx context.Context, c chan promptResult) {
|
||||
t.Helper()
|
||||
|
||||
_, err := fmt.Fprint(promptWriter, "n\n")
|
||||
assert.NilError(t, err)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("PromptForConfirmation did not return after user input `n`")
|
||||
case r := <-c:
|
||||
assert.Check(t, !r.result)
|
||||
assert.NilError(t, r.err)
|
||||
}
|
||||
}},
|
||||
{"yes", func(t *testing.T, ctx context.Context, c chan promptResult) {
|
||||
t.Helper()
|
||||
|
||||
_, err := fmt.Fprint(promptWriter, "y\n")
|
||||
assert.NilError(t, err)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("PromptForConfirmation did not return after user input `y`")
|
||||
case r := <-c:
|
||||
assert.Check(t, r.result)
|
||||
assert.NilError(t, r.err)
|
||||
}
|
||||
}},
|
||||
{"any", func(t *testing.T, ctx context.Context, c chan promptResult) {
|
||||
t.Helper()
|
||||
|
||||
_, err := fmt.Fprint(promptWriter, "a\n")
|
||||
assert.NilError(t, err)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("PromptForConfirmation did not return after user input `a`")
|
||||
case r := <-c:
|
||||
assert.Check(t, !r.result)
|
||||
assert.NilError(t, r.err)
|
||||
}
|
||||
}},
|
||||
{"with space", func(t *testing.T, ctx context.Context, c chan promptResult) {
|
||||
t.Helper()
|
||||
|
||||
_, err := fmt.Fprint(promptWriter, " y\n")
|
||||
assert.NilError(t, err)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("PromptForConfirmation did not return after user input ` y`")
|
||||
case r := <-c:
|
||||
assert.Check(t, r.result)
|
||||
assert.NilError(t, r.err)
|
||||
}
|
||||
}},
|
||||
{"reader closed", func(t *testing.T, ctx context.Context, c chan promptResult) {
|
||||
t.Helper()
|
||||
|
||||
assert.NilError(t, promptReader.Close())
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("PromptForConfirmation did not return after promptReader was closed")
|
||||
case r := <-c:
|
||||
assert.Check(t, !r.result)
|
||||
assert.NilError(t, r.err)
|
||||
}
|
||||
}},
|
||||
} {
|
||||
t.Run("case="+tc.desc, func(t *testing.T) {
|
||||
buf.Reset()
|
||||
promptReader, promptWriter = io.Pipe()
|
||||
|
||||
wroteHook := make(chan struct{}, 1)
|
||||
defer close(wroteHook)
|
||||
promptOut := test.NewWriterWithHook(bufioWriter, func(p []byte) {
|
||||
wroteHook <- struct{}{}
|
||||
})
|
||||
|
||||
result := make(chan promptResult, 1)
|
||||
defer close(result)
|
||||
go func() {
|
||||
r, err := command.PromptForConfirmation(ctx, promptReader, promptOut, "")
|
||||
result <- promptResult{r, err}
|
||||
}()
|
||||
|
||||
// wait for the Prompt to write to the buffer
|
||||
pollForPromptOutput(ctx, t, wroteHook)
|
||||
drainChannel(ctx, wroteHook)
|
||||
|
||||
assert.NilError(t, bufioWriter.Flush())
|
||||
assert.Equal(t, strings.TrimSpace(buf.String()), "Are you sure you want to proceed? [y/N]")
|
||||
|
||||
resultCtx, resultCancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
defer resultCancel()
|
||||
|
||||
tc.f(t, resultCtx, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func drainChannel(ctx context.Context, ch <-chan struct{}) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ch:
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func pollForPromptOutput(ctx context.Context, t *testing.T, wroteHook <-chan struct{}) {
|
||||
t.Helper()
|
||||
|
||||
ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
defer cancel()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("Prompt output was not written to before ctx was cancelled")
|
||||
return
|
||||
case <-wroteHook:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +80,10 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
|
||||
// API < v1.42 removes all volumes (anonymous and named) by default.
|
||||
warning = allVolumesWarning
|
||||
}
|
||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return 0, "", errdefs.Cancelled(errors.New("user cancelled operation"))
|
||||
if !options.force {
|
||||
if r, err := command.PromptForConfirmation(ctx, dockerCli.In(), dockerCli.Out(), warning); !r || err != nil {
|
||||
return 0, "", errdefs.Cancelled(errors.New("user cancelled operation"))
|
||||
}
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().VolumesPrune(ctx, pruneFilters)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package volume
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
@@ -183,3 +184,19 @@ func simplePruneFunc(filters.Args) (types.VolumesPruneReport, error) {
|
||||
SpaceReclaimed: 2000,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestVolumePrunePromptTerminate(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
volumePruneFunc: func(filter filters.Args) (types.VolumesPruneReport, error) {
|
||||
return types.VolumesPruneReport{}, errors.New("fakeClient volumePruneFunc should not be called")
|
||||
},
|
||||
})
|
||||
|
||||
cmd := NewPruneCommand(cli)
|
||||
test.TerminatePrompt(ctx, t, cmd, cli, nil)
|
||||
|
||||
golden.Assert(t, cli.OutBuffer().String(), "volume-prune-terminate.golden")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
WARNING! This will remove anonymous local volumes not used by at least one container.
|
||||
Are you sure you want to continue? [y/N]
|
||||
+12
-5
@@ -69,7 +69,7 @@ func processBuilder(dockerCli command.Cli, cmd *cobra.Command, args, osargs []st
|
||||
}
|
||||
|
||||
// is this a build that should be forwarded to the builder?
|
||||
fwargs, fwosargs, forwarded := forwardBuilder(builderAlias, args, osargs)
|
||||
fwargs, fwosargs, fwcmdpath, forwarded := forwardBuilder(builderAlias, args, osargs)
|
||||
if !forwarded {
|
||||
return args, osargs, nil, nil
|
||||
}
|
||||
@@ -116,31 +116,38 @@ func processBuilder(dockerCli command.Cli, cmd *cobra.Command, args, osargs []st
|
||||
envs = append([]string{"BUILDX_BUILDER=" + dockerCli.CurrentContext()}, envs...)
|
||||
}
|
||||
|
||||
// overwrite the command path for this plugin using the alias name.
|
||||
cmd.Annotations[pluginmanager.CommandAnnotationPluginCommandPath] = strings.Join(append([]string{cmd.CommandPath()}, fwcmdpath...), " ")
|
||||
|
||||
return fwargs, fwosargs, envs, nil
|
||||
}
|
||||
|
||||
func forwardBuilder(alias string, args, osargs []string) ([]string, []string, bool) {
|
||||
aliases := [][2][]string{
|
||||
func forwardBuilder(alias string, args, osargs []string) ([]string, []string, []string, bool) {
|
||||
aliases := [][3][]string{
|
||||
{
|
||||
{"builder"},
|
||||
{alias},
|
||||
{"builder"},
|
||||
},
|
||||
{
|
||||
{"build"},
|
||||
{alias, "build"},
|
||||
{},
|
||||
},
|
||||
{
|
||||
{"image", "build"},
|
||||
{alias, "build"},
|
||||
{"image"},
|
||||
},
|
||||
}
|
||||
for _, al := range aliases {
|
||||
if fwargs, changed := command.StringSliceReplaceAt(args, al[0], al[1], 0); changed {
|
||||
fwosargs, _ := command.StringSliceReplaceAt(osargs, al[0], al[1], -1)
|
||||
return fwargs, fwosargs, true
|
||||
fwcmdpath := al[2]
|
||||
return fwargs, fwosargs, fwcmdpath, true
|
||||
}
|
||||
}
|
||||
return args, osargs, false
|
||||
return args, osargs, nil, false
|
||||
}
|
||||
|
||||
// hasBuilderName checks if a builder name is defined in args or env vars
|
||||
|
||||
+3
-2
@@ -1,5 +1,5 @@
|
||||
variable "GO_VERSION" {
|
||||
default = "1.21.7"
|
||||
default = "1.21.9"
|
||||
}
|
||||
variable "VERSION" {
|
||||
default = ""
|
||||
@@ -186,6 +186,7 @@ target "bin-image-cross" {
|
||||
"linux/arm64",
|
||||
"linux/ppc64le",
|
||||
"linux/s390x",
|
||||
"windows/amd64"
|
||||
"windows/amd64",
|
||||
"windows/arm64"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.21.7
|
||||
ARG GO_VERSION=1.21.9
|
||||
ARG ALPINE_VERSION=3.18
|
||||
|
||||
ARG BUILDX_VERSION=0.12.1
|
||||
@@ -11,7 +11,7 @@ ENV GOTOOLCHAIN=local
|
||||
ENV CGO_ENABLED=0
|
||||
|
||||
FROM golang AS gofumpt
|
||||
ARG GOFUMPT_VERSION=v0.4.0
|
||||
ARG GOFUMPT_VERSION=v0.6.0
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=tmpfs,target=/go/src/ \
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.21.7
|
||||
ARG GO_VERSION=1.21.9
|
||||
ARG ALPINE_VERSION=3.18
|
||||
ARG GOLANGCI_LINT_VERSION=v1.55.2
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.21.7
|
||||
ARG GO_VERSION=1.21.9
|
||||
ARG ALPINE_VERSION=3.18
|
||||
ARG MODOUTDATED_VERSION=v0.8.0
|
||||
|
||||
|
||||
+71
-9
@@ -50,8 +50,10 @@ The table below provides an overview of the current status of deprecated feature
|
||||
|
||||
| Status | Feature | Deprecated | Remove |
|
||||
|------------|------------------------------------------------------------------------------------------------------------------------------------|------------|--------|
|
||||
| Deprecated | [Unauthenticated TCP connections](#unauthenticated-tcp-connections) | v26.0 | v27.0 |
|
||||
| Deprecated | [`Container` and `ContainerConfig` fields in Image inspect](#container-and-containerconfig-fields-in-image-inspect) | v25.0 | v26.0 |
|
||||
| Deprecated | [Deprecate legacy API versions](#deprecate-legacy-api-versions) | v25.0 | v26.0 |
|
||||
| Deprecated | [Container short ID in network Aliases field](#container-short-id-in-network-aliases-field) | v25.0 | v26.0 |
|
||||
| Removed | [Container short ID in network Aliases field](#container-short-id-in-network-aliases-field) | v25.0 | v26.0 |
|
||||
| Deprecated | [IsAutomated field, and "is-automated" filter on docker search](#isautomated-field-and-is-automated-filter-on-docker-search) | v25.0 | v26.0 |
|
||||
| Removed | [logentries logging driver](#logentries-logging-driver) | v24.0 | v25.0 |
|
||||
| Removed | [OOM-score adjust for the daemon](#oom-score-adjust-for-the-daemon) | v24.0 | v25.0 |
|
||||
@@ -72,7 +74,7 @@ The table below provides an overview of the current status of deprecated feature
|
||||
| Removed | [`docker build --stream` flag (experimental)](#docker-build---stream-flag-experimental) | v20.10 | v20.10 |
|
||||
| Deprecated | [`fluentd-async-connect` log opt](#fluentd-async-connect-log-opt) | v20.10 | - |
|
||||
| Removed | [Configuration options for experimental CLI features](#configuration-options-for-experimental-cli-features) | v19.03 | v23.0 |
|
||||
| Deprecated | [Pushing and pulling with image manifest v2 schema 1](#pushing-and-pulling-with-image-manifest-v2-schema-1) | v19.03 | v20.10 |
|
||||
| Deprecated | [Pushing and pulling with image manifest v2 schema 1](#pushing-and-pulling-with-image-manifest-v2-schema-1) | v19.03 | v27.0 |
|
||||
| Removed | [`docker engine` subcommands](#docker-engine-subcommands) | v19.03 | v20.10 |
|
||||
| Removed | [Top-level `docker deploy` subcommand (experimental)](#top-level-docker-deploy-subcommand-experimental) | v19.03 | v20.10 |
|
||||
| Removed | [`docker stack deploy` using "dab" files (experimental)](#docker-stack-deploy-using-dab-files-experimental) | v19.03 | v20.10 |
|
||||
@@ -110,6 +112,46 @@ The table below provides an overview of the current status of deprecated feature
|
||||
| Removed | [`--run` flag on `docker commit`](#--run-flag-on-docker-commit) | v0.10 | v1.13 |
|
||||
| Removed | [Three arguments form in `docker import`](#three-arguments-form-in-docker-import) | v0.6.7 | v1.12 |
|
||||
|
||||
### Unauthenticated TCP connections
|
||||
|
||||
**Deprecated in Release: v26.0**
|
||||
**Target For Removal In Release: v27.0**
|
||||
|
||||
Configuring the Docker daemon to listen on a TCP address will require mandatory
|
||||
TLS verification. This change aims to ensure secure communication by preventing
|
||||
unauthorized access to the Docker daemon over potentially insecure networks.
|
||||
This mandatory TLS requirement applies to all TCP addresses except `tcp://localhost`.
|
||||
|
||||
In version 27.0 and later, specifying `--tls=false` or `--tlsverify=false` CLI flags
|
||||
causes the daemon to fail to start if it's also configured to accept remote connections over TCP.
|
||||
This also applies to the equivalent configuration options in `daemon.json`.
|
||||
|
||||
To facilitate remote access to the Docker daemon over TCP, you'll need to
|
||||
implement TLS verification. This secures the connection by encrypting data in
|
||||
transit and providing a mechanism for mutual authentication.
|
||||
|
||||
For environments remote daemon access isn't required,
|
||||
we recommend binding the Docker daemon to a Unix socket.
|
||||
For daemon's where remote access is required and where TLS encryption is not feasible,
|
||||
you may want to consider using SSH as an alternative solution.
|
||||
|
||||
For further information, assistance, and step-by-step instructions on
|
||||
configuring TLS (or SSH) for the Docker daemon, refer to
|
||||
[Protect the Docker daemon socket](https://docs.docker.com/engine/security/protect-access/).
|
||||
|
||||
### `Container` and `ContainerConfig` fields in Image inspect
|
||||
|
||||
**Deprecated in Release: v25.0**
|
||||
**Target For Removal In Release: v26.0**
|
||||
|
||||
The `Container` and `ContainerConfig` fields returned by `docker inspect` are
|
||||
mostly an implementation detail of the classic (non-BuildKit) image builder.
|
||||
These fields are not portable and are empty when using the
|
||||
BuildKit-based builder (enabled by default since v23.0).
|
||||
These fields are deprecated in v25.0 and will be omitted starting from v26.0.
|
||||
If image configuration of an image is needed, you can obtain it from the
|
||||
`Config` field.
|
||||
|
||||
### Deprecate legacy API versions
|
||||
|
||||
**Deprecated in Release: v25.0**
|
||||
@@ -167,7 +209,7 @@ old clients, and those clients must be supported.
|
||||
### Container short ID in network Aliases field
|
||||
|
||||
**Deprecated in Release: v25.0**
|
||||
**Target For Remove In Release: v26.0**
|
||||
**Removed In Release: v26.0**
|
||||
|
||||
The `Aliases` field returned by `docker inspect` contains the container short
|
||||
ID once the container is started. This behavior is deprecated in v25.0 but
|
||||
@@ -558,15 +600,35 @@ for the old option will be removed in a future release.
|
||||
|
||||
**Deprecated in Release: v19.03**
|
||||
|
||||
**Target For Removal In Release: v20.10**
|
||||
**Disabled by default in Release: v26.0**
|
||||
|
||||
The image manifest
|
||||
[v2 schema 1](https://github.com/docker/distribution/blob/fda42e5ef908bdba722d435ff1f330d40dfcd56c/docs/spec/manifest-v2-1.md)
|
||||
format is deprecated in favor of the
|
||||
[v2 schema 2](https://github.com/docker/distribution/blob/fda42e5ef908bdba722d435ff1f330d40dfcd56c/docs/spec/manifest-v2-2.md) format.
|
||||
**Target For Removal In Release: v27.0**
|
||||
|
||||
If the registry you are using still supports v2 schema 1, urge their administrators to move to v2 schema 2.
|
||||
The image manifest [v2 schema 1](https://distribution.github.io/distribution/spec/deprecated-schema-v1/)
|
||||
and "Docker Image v1" formats were deprecated in favor of the
|
||||
[v2 schema 2](https://distribution.github.io/distribution/spec/manifest-v2-2/)
|
||||
and [OCI image spec](https://github.com/opencontainers/image-spec/tree/v1.1.0)
|
||||
formats.
|
||||
|
||||
These legacy formats should no longer be used, and users are recommended to
|
||||
update images to use current formats, or to upgrade to more current images.
|
||||
Starting with Docker v26.0, pulling these images is disabled by default, and
|
||||
produces an error when attempting to pull the image:
|
||||
|
||||
```console
|
||||
$ docker pull ubuntu:10.04
|
||||
Error response from daemon:
|
||||
[DEPRECATION NOTICE] Docker Image Format v1 and Docker Image manifest version 2, schema 1 support is disabled by default and will be removed in an upcoming release.
|
||||
Suggest the author of docker.io/library/ubuntu:10.04 to upgrade the image to the OCI Format or Docker Image manifest v2, schema 2.
|
||||
More information at https://docs.docker.com/go/deprecated-image-specs/
|
||||
```
|
||||
|
||||
An environment variable (`DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE`) is
|
||||
added in Docker v26.0 that allows re-enabling support for these image formats
|
||||
in the daemon. This environment variable must be set to a non-empty value in
|
||||
the daemon's environment (for example, through a [systemd override file](https://docs.docker.com/config/daemon/systemd/)).
|
||||
Support for the `DOCKER_ENABLE_DEPRECATED_PULL_SCHEMA_1_IMAGE` environment-variable
|
||||
will be removed in Docker v27.0 after which this functionality is removed permanently.
|
||||
|
||||
### `docker engine` subcommands
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ Execute a command in a running container
|
||||
| [`-e`](#env), [`--env`](#env) | `list` | | Set environment variables |
|
||||
| `--env-file` | `list` | | Read in a file of environment variables |
|
||||
| `-i`, `--interactive` | | | Keep STDIN open even if not attached |
|
||||
| `--privileged` | | | Give extended privileges to the command |
|
||||
| [`--privileged`](#privileged) | | | Give extended privileges to the command |
|
||||
| `-t`, `--tty` | | | Allocate a pseudo-TTY |
|
||||
| `-u`, `--user` | `string` | | Username or UID (format: `<name\|uid>[:<group\|gid>]`) |
|
||||
| [`-w`](#workdir), [`--workdir`](#workdir) | `string` | | Working directory inside the container |
|
||||
@@ -96,6 +96,10 @@ VAR_B=2
|
||||
HOME=/root
|
||||
```
|
||||
|
||||
### <a name="privileged"></a> Escalate container privileges (--privileged)
|
||||
|
||||
See [`docker run --privileged`](container_run.md#privileged).
|
||||
|
||||
### <a name="workdir"></a> Set the working directory for the exec process (--workdir, -w)
|
||||
|
||||
By default `docker exec` command runs in the same working directory set when
|
||||
|
||||
@@ -108,7 +108,7 @@ Create and run a new container from an image
|
||||
| [`-t`](#tty), [`--tty`](#tty) | | | Allocate a pseudo-TTY |
|
||||
| [`--ulimit`](#ulimit) | `ulimit` | | Ulimit options |
|
||||
| `-u`, `--user` | `string` | | Username or UID (format: <name\|uid>[:<group\|gid>]) |
|
||||
| `--userns` | `string` | | User namespace to use |
|
||||
| [`--userns`](#userns) | `string` | | User namespace to use |
|
||||
| [`--uts`](#uts) | `string` | | UTS namespace to use |
|
||||
| [`-v`](#volume), [`--volume`](#volume) | `list` | | Bind mount a volume |
|
||||
| `--volume-driver` | `string` | | Optional volume driver for the container |
|
||||
@@ -271,6 +271,21 @@ container.
|
||||
strace: Process 1 attached
|
||||
```
|
||||
|
||||
### <a name="userns"></a> Disable namespace remapping for a container (--userns)
|
||||
|
||||
If you enable user namespaces on the daemon,
|
||||
all containers are started with user namespaces enabled by default.
|
||||
To disable user namespace remapping for a specific container,
|
||||
you can set the `--userns` flag to `host`.
|
||||
|
||||
```console
|
||||
docker run --userns=host hello-world
|
||||
```
|
||||
|
||||
`host` is the only valid value for the `--userns` flag.
|
||||
|
||||
For more information, refer to [Isolate containers with a user namespace](https://docs.docker.com/engine/security/userns-remap/).
|
||||
|
||||
### <a name="uts"></a> UTS settings (--uts)
|
||||
|
||||
```text
|
||||
@@ -326,7 +341,37 @@ are broken into multiple containers, you might need to share the IPC mechanisms
|
||||
of the containers, using `"shareable"` mode for the main (i.e. "donor")
|
||||
container, and `"container:<donor-name-or-ID>"` for other containers.
|
||||
|
||||
### <a name="privileged"></a> Full container capabilities (--privileged)
|
||||
### <a name="privileged"></a> Escalate container privileges (--privileged)
|
||||
|
||||
The `--privileged` flag gives the following capabilities to a container:
|
||||
|
||||
- Enables all Linux kernel capabilities
|
||||
- Disables the default seccomp profile
|
||||
- Disables the default AppArmor profile
|
||||
- Disables the SELinux process label
|
||||
- Grants access to all host devices
|
||||
- Makes `/sys` read-write
|
||||
- Makes cgroups mounts read-write
|
||||
|
||||
In other words, the container can then do almost everything that the host can
|
||||
do. This flag exists to allow special use-cases, like running Docker within
|
||||
Docker.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
> Use the `--privileged` flag with caution.
|
||||
> A container with `--privileged` is not a securely sandboxed process.
|
||||
> Containers in this mode can get a root shell on the host
|
||||
> and take control over the system.
|
||||
>
|
||||
> For most use cases, this flag should not be the preferred solution.
|
||||
> If your container requires escalated privileges,
|
||||
> you should prefer to explicitly grant the necessary permissions,
|
||||
> for example by adding individual kernel capabilities with `--cap-add`.
|
||||
>
|
||||
> For more information, see
|
||||
> [Runtime privilege and Linux capabilities](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
|
||||
{ .warning }
|
||||
|
||||
The following example doesn't work, because by default, Docker drops most
|
||||
potentially dangerous kernel capabilities, including `CAP_SYS_ADMIN ` (which is
|
||||
@@ -348,11 +393,6 @@ Filesystem Size Used Avail Use% Mounted on
|
||||
none 1.9G 0 1.9G 0% /mnt
|
||||
```
|
||||
|
||||
The `--privileged` flag gives all capabilities to the container, and it also
|
||||
lifts all the limitations enforced by the `device` cgroup controller. In other
|
||||
words, the container can then do almost everything that the host can do. This
|
||||
flag exists to allow special use-cases, like running Docker within Docker.
|
||||
|
||||
### <a name="workdir"></a> Set working directory (-w, --workdir)
|
||||
|
||||
```console
|
||||
@@ -1000,7 +1040,7 @@ text. However, if you try the same thing but also adding the `-t` flag, the
|
||||
password is hidden:
|
||||
|
||||
```console
|
||||
$ docker run -i debian passwd root
|
||||
$ docker run -it debian passwd root
|
||||
New password:
|
||||
Retype new password:
|
||||
passwd: password updated successfully
|
||||
|
||||
@@ -69,7 +69,7 @@ user credentials, VPNs, and so forth.
|
||||
> **Note**
|
||||
>
|
||||
> If the `URL` parameter contains a fragment the system recursively clones
|
||||
> the repository and its submodules using a `git clone --recursive` command.
|
||||
> the repository and its submodules.
|
||||
|
||||
Git URLs accept context configuration in their fragment section, separated by a
|
||||
colon (`:`). The first part represents the reference that Git checks out,
|
||||
|
||||
@@ -162,7 +162,8 @@ equivalent Docker daemon flags used for docker0 bridge:
|
||||
| `com.docker.network.container_iface_prefix` | - | Set a custom prefix for container interfaces |
|
||||
|
||||
The following arguments can be passed to `docker network create` for any
|
||||
network driver, again with their approximate equivalents to `docker daemon`.
|
||||
network driver, again with their approximate equivalents to Docker daemon
|
||||
flags used for the docker0 bridge:
|
||||
|
||||
| Argument | Equivalent | Description |
|
||||
|--------------|----------------|--------------------------------------------|
|
||||
@@ -183,6 +184,12 @@ $ docker network create \
|
||||
|
||||
### <a name="internal"></a> Network internal mode (--internal)
|
||||
|
||||
Containers on an internal network may communicate between each other, but not
|
||||
with any other network, as no default route is configured and firewall rules
|
||||
are set up to drop all traffic to or from other networks. Communication with
|
||||
the gateway IP address (and thus appropriately configured host services) is
|
||||
possible, and the host may communicate with any container IP directly.
|
||||
|
||||
By default, when you connect a container to an `overlay` network, Docker also
|
||||
connects a bridge network to it to provide external connectivity. If you want
|
||||
to create an externally isolated `overlay` network, you can specify the
|
||||
|
||||
@@ -12,7 +12,9 @@ Deploy a new stack or update an existing stack
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------------------------|:--------------|:---------|:--------------------------------------------------------------------------------------------------|
|
||||
| [`-c`](#compose-file), [`--compose-file`](#compose-file) | `stringSlice` | | Path to a Compose file, or `-` to read from stdin |
|
||||
| `-d`, `--detach` | `bool` | `true` | Exit immediately instead of waiting for the stack services to converge |
|
||||
| `--prune` | | | Prune services that are no longer referenced |
|
||||
| `-q`, `--quiet` | | | Suppress progress output |
|
||||
| `--resolve-image` | `string` | `always` | Query the registry to resolve image digest and supported platforms (`always`, `changed`, `never`) |
|
||||
| `--with-registry-auth` | | | Send registry authentication details to Swarm agents |
|
||||
|
||||
|
||||
@@ -7,6 +7,12 @@ Remove one or more stacks
|
||||
|
||||
`docker stack rm`, `docker stack remove`, `docker stack down`
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-----------------|:-------|:--------|:------------------------------|
|
||||
| `-d`, `--detach` | `bool` | `true` | Do not wait for stack removal |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
|
||||
@@ -1347,7 +1347,7 @@ using the `daemon.json` file.
|
||||
"default-network-opts": {
|
||||
"bridge": {
|
||||
"com.docker.network.bridge.host_binding_ipv4": "127.0.0.1",
|
||||
"com.docker.network.bridge.mtu": "1234"
|
||||
"com.docker.network.driver.mtu": "1234"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1363,7 +1363,7 @@ you create use these option configurations as defaults.
|
||||
```console
|
||||
$ docker network create mynet
|
||||
$ docker network inspect mynet --format "{{json .Options}}"
|
||||
{"com.docker.network.bridge.host_binding_ipv4":"127.0.0.1","com.docker.network.bridge.mtu":"1234"}
|
||||
{"com.docker.network.bridge.host_binding_ipv4":"127.0.0.1","com.docker.network.driver.mtu":"1234"}
|
||||
```
|
||||
|
||||
Note that changing this daemon configuration doesn't affect pre-existing
|
||||
@@ -1377,5 +1377,5 @@ daemon configuration. The CLI flag expects a value with the following format:
|
||||
```console
|
||||
$ sudo dockerd \
|
||||
--default-network-opt bridge=com.docker.network.bridge.host_binding_ipv4=127.0.0.1 \
|
||||
--default-network-opt bridge=com.docker.network.bridge.mtu=1234
|
||||
--default-network-opt bridge=com.docker.network.driver.mtu=1234
|
||||
```
|
||||
|
||||
@@ -7,7 +7,7 @@ title: Running containers
|
||||
---
|
||||
|
||||
Docker runs processes in isolated containers. A container is a process
|
||||
which runs on a host. The host may be local or remote. When an you
|
||||
which runs on a host. The host may be local or remote. When you
|
||||
execute `docker run`, the container process that runs is isolated in
|
||||
that it has its own file system, its own networking, and its own
|
||||
isolated process tree separate from the host.
|
||||
@@ -767,7 +767,7 @@ For example, this command creates a container and limits the read rate to
|
||||
`1000` IO per second from `/dev/sda`:
|
||||
|
||||
```console
|
||||
$ docker run -ti --device-read-iops /dev/sda:1000 ubuntu
|
||||
$ docker run -it --device-read-iops /dev/sda:1000 ubuntu
|
||||
```
|
||||
|
||||
The `--device-write-iops` flag limits write rate (IO per second) to a device.
|
||||
@@ -775,7 +775,7 @@ For example, this command creates a container and limits the write rate to
|
||||
`1000` IO per second to `/dev/sda`:
|
||||
|
||||
```console
|
||||
$ docker run -ti --device-write-iops /dev/sda:1000 ubuntu
|
||||
$ docker run -it --device-write-iops /dev/sda:1000 ubuntu
|
||||
```
|
||||
|
||||
Both flags take limits in the `<device-path>:<limit>` format. Both read and
|
||||
@@ -813,11 +813,12 @@ by default a container is not allowed to access any devices, but a
|
||||
the documentation on [cgroups devices](https://www.kernel.org/doc/Documentation/cgroup-v1/devices.txt)).
|
||||
|
||||
The `--privileged` flag gives all capabilities to the container. When the operator
|
||||
executes `docker run --privileged`, Docker will enable access to all devices on
|
||||
the host as well as set some configuration in AppArmor or SELinux to allow the
|
||||
container nearly all the same access to the host as processes running outside
|
||||
containers on the host. Additional information about running with `--privileged`
|
||||
is available on the [Docker Blog](https://www.docker.com/blog/docker-can-now-run-within-docker/).
|
||||
executes `docker run --privileged`, Docker enables access to all devices on
|
||||
the host, and reconfigures AppArmor or SELinux to allow the container
|
||||
nearly all the same access to the host as processes running outside
|
||||
containers on the host. Use this flag with caution.
|
||||
For more information about the `--privileged` flag, see the
|
||||
[`docker run` reference](https://docs.docker.com/reference/cli/docker/container/run/#privileged).
|
||||
|
||||
If you want to limit access to a specific device or devices you can use
|
||||
the `--device` flag. It allows you to specify one or more devices that
|
||||
|
||||
@@ -35,7 +35,7 @@ func TestGlobalHelp(t *testing.T) {
|
||||
for _, s := range []string{
|
||||
`Management Commands:`,
|
||||
`\s+container\s+Manage containers`,
|
||||
`\s+helloworld\*\s+A basic Hello World plugin for tests \(Docker Inc\., testing\)`,
|
||||
`\s+helloworld\*\s+A basic Hello World plugin for tests`,
|
||||
`\s+image\s+Manage images`,
|
||||
`Commands:`,
|
||||
`\s+create\s+Create a new container`,
|
||||
|
||||
+3
@@ -9,7 +9,10 @@ Aliases:
|
||||
Options:
|
||||
-c, --compose-file strings Path to a Compose file, or "-" to read
|
||||
from stdin
|
||||
-d, --detach Exit immediately instead of waiting for
|
||||
the stack services to converge (default true)
|
||||
--prune Prune services that are no longer referenced
|
||||
-q, --quiet Suppress progress output
|
||||
--resolve-image string Query the registry to resolve image digest
|
||||
and supported platforms ("always",
|
||||
"changed", "never") (default "always")
|
||||
|
||||
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.21.7
|
||||
ARG GO_VERSION=1.21.9
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine AS generated
|
||||
ENV GOTOOLCHAIN=local
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/spf13/cobra"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TerminatePrompt(ctx context.Context, t *testing.T, cmd *cobra.Command, cli *FakeCli, assertFunc func(*testing.T, error)) {
|
||||
t.Helper()
|
||||
|
||||
errChan := make(chan error)
|
||||
defer close(errChan)
|
||||
|
||||
// wrap the out stream to detect when the prompt is ready
|
||||
writerHookChan := make(chan struct{})
|
||||
defer close(writerHookChan)
|
||||
|
||||
outStream := streams.NewOut(NewWriterWithHook(cli.OutBuffer(), func(p []byte) {
|
||||
writerHookChan <- struct{}{}
|
||||
}))
|
||||
cli.SetOut(outStream)
|
||||
|
||||
r, _, err := os.Pipe()
|
||||
assert.NilError(t, err)
|
||||
cli.SetIn(streams.NewIn(r))
|
||||
|
||||
go func() {
|
||||
errChan <- cmd.ExecuteContext(ctx)
|
||||
}()
|
||||
|
||||
writeCtx, writeCancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
defer writeCancel()
|
||||
|
||||
// wait for the prompt to be ready
|
||||
select {
|
||||
case <-writeCtx.Done():
|
||||
t.Fatalf("command %s did not write prompt to stdout", cmd.Name())
|
||||
case <-writerHookChan:
|
||||
// drain the channel for future buffer writes
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-writerHookChan:
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
assert.Check(t, cli.OutBuffer().Len() > 0)
|
||||
|
||||
// a small delay to ensure the plugin is prompting
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
|
||||
errCtx, errCancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
defer errCancel()
|
||||
|
||||
// sigint and sigterm are caught by the prompt
|
||||
// this allows us to gracefully exit the prompt with a 0 exit code
|
||||
syscall.Kill(syscall.Getpid(), syscall.SIGINT)
|
||||
|
||||
select {
|
||||
case <-errCtx.Done():
|
||||
t.Logf("command stdout:\n%s\n", cli.OutBuffer().String())
|
||||
t.Logf("command stderr:\n%s\n", cli.ErrBuffer().String())
|
||||
t.Fatalf("command %s did not return after SIGINT", cmd.Name())
|
||||
case err := <-errChan:
|
||||
if assertFunc != nil {
|
||||
assertFunc(t, err)
|
||||
} else {
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, cli.ErrBuffer().String(), "")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
// WriterWithHook is an io.Writer that calls a hook function
|
||||
// after every write.
|
||||
// This is useful in testing to wait for a write to complete,
|
||||
// or to check what was written.
|
||||
// To create a WriterWithHook use the NewWriterWithHook function.
|
||||
type WriterWithHook struct {
|
||||
actualWriter io.Writer
|
||||
hook func([]byte)
|
||||
}
|
||||
|
||||
// Write writes p to the actual writer and then calls the hook function.
|
||||
func (w *WriterWithHook) Write(p []byte) (n int, err error) {
|
||||
defer w.hook(p)
|
||||
return w.actualWriter.Write(p)
|
||||
}
|
||||
|
||||
var _ io.Writer = (*WriterWithHook)(nil)
|
||||
|
||||
// NewWriterWithHook returns a new WriterWithHook that still writes to the actualWriter
|
||||
// but also calls the hook function after every write.
|
||||
// The hook function is useful for testing, or waiting for a write to complete.
|
||||
func NewWriterWithHook(actualWriter io.Writer, hook func([]byte)) *WriterWithHook {
|
||||
return &WriterWithHook{actualWriter: actualWriter, hook: hook}
|
||||
}
|
||||
+1
-1
@@ -23,7 +23,7 @@ EOL
|
||||
}
|
||||
|
||||
update() {
|
||||
(set -x ; go mod tidy -compat=1.19 -modfile=vendor.mod; go mod vendor -modfile=vendor.mod)
|
||||
(set -x ; go mod tidy -modfile=vendor.mod; go mod vendor -modfile=vendor.mod)
|
||||
}
|
||||
|
||||
validate() {
|
||||
|
||||
+10
-10
@@ -4,15 +4,15 @@ module github.com/docker/cli
|
||||
// There is no 'go.mod' file, as that would imply opting in for all the rules
|
||||
// around SemVer, which this repo cannot abide by as it uses CalVer.
|
||||
|
||||
go 1.19
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.0
|
||||
github.com/containerd/containerd v1.7.13
|
||||
github.com/containerd/containerd v1.7.14
|
||||
github.com/creack/pty v1.1.21
|
||||
github.com/distribution/reference v0.5.0
|
||||
github.com/docker/distribution v2.8.3+incompatible
|
||||
github.com/docker/docker v25.0.1-0.20240226093800-c70d7905fbd9+incompatible // master (v26.0.0-dev)
|
||||
github.com/docker/docker v26.0.2-0.20240418155034-7cef0d9cd1cf+incompatible
|
||||
github.com/docker/docker-credential-helpers v0.8.1
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
@@ -37,9 +37,10 @@ require (
|
||||
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20200727161949-f79598599c5d
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
go.opentelemetry.io/otel v1.21.0
|
||||
golang.org/x/sync v0.6.0
|
||||
golang.org/x/sys v0.16.0
|
||||
golang.org/x/term v0.15.0
|
||||
golang.org/x/sys v0.18.0
|
||||
golang.org/x/term v0.18.0
|
||||
golang.org/x/text v0.14.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gotest.tools/v3 v3.5.1
|
||||
@@ -59,7 +60,7 @@ require (
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/go-logr/logr v1.3.0 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/gorilla/mux v1.8.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/klauspost/compress v1.17.4 // indirect
|
||||
@@ -77,15 +78,14 @@ require (
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
go.etcd.io/etcd/raft/v3 v3.5.6 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1 // indirect
|
||||
go.opentelemetry.io/otel v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.21.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.21.0 // indirect
|
||||
golang.org/x/crypto v0.17.0 // indirect
|
||||
golang.org/x/crypto v0.21.0 // indirect
|
||||
golang.org/x/mod v0.14.0 // indirect
|
||||
golang.org/x/net v0.19.0 // indirect
|
||||
golang.org/x/net v0.23.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/tools v0.16.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect
|
||||
google.golang.org/grpc v1.59.0 // indirect
|
||||
google.golang.org/protobuf v1.31.0 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
)
|
||||
|
||||
+35
-16
@@ -1,6 +1,7 @@
|
||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
@@ -27,17 +28,19 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0Bsq
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
|
||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||
github.com/cloudflare/cfssl v1.6.4 h1:NMOvfrEjFfC63K3SGXgAnFdsgkmiq4kATme5BfcqrO8=
|
||||
github.com/cloudflare/cfssl v1.6.4/go.mod h1:8b3CQMxfWPAeom3zBnGJ6sd+G1NkL5TXqmDXacb+1J0=
|
||||
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
|
||||
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
|
||||
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
|
||||
github.com/containerd/containerd v1.7.13 h1:wPYKIeGMN8vaggSKuV1X0wZulpMz4CrgEsZdaCyB6Is=
|
||||
github.com/containerd/containerd v1.7.13/go.mod h1:zT3up6yTRfEUa6+GsITYIJNgSVL9NQ4x4h1RPzk0Wu4=
|
||||
github.com/containerd/containerd v1.7.14 h1:H/XLzbnGuenZEGK+v0RkwTdv2u1QFAruMe5N0GNPJwA=
|
||||
github.com/containerd/containerd v1.7.14/go.mod h1:YMC9Qt5yzNqXx/fO4j/5yYVIHXSRrlB3H7sxkUTvspg=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
@@ -54,8 +57,8 @@ github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5
|
||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v25.0.1-0.20240226093800-c70d7905fbd9+incompatible h1:f+nMTocctRJkIsKEGp/IDFaOJ0arCVGW+ptwcfkXaLQ=
|
||||
github.com/docker/docker v25.0.1-0.20240226093800-c70d7905fbd9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v26.0.2-0.20240418155034-7cef0d9cd1cf+incompatible h1:RfKz/iws/BQwbCPPTRz0QXFRwvPAH3kYGwjaVKlh6jc=
|
||||
github.com/docker/docker v26.0.2-0.20240418155034-7cef0d9cd1cf+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.8.1 h1:j/eKUktUltBtMzKqmfLB0PAgqYyMHOp5vfsD1807oKo=
|
||||
github.com/docker/docker-credential-helpers v0.8.1/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||
@@ -104,10 +107,11 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/certificate-transparency-go v1.1.4 h1:hCyXHDbtqlr/lMXU0D4WgbalXL0Zk4dSWWMbPV8VrqY=
|
||||
github.com/google/certificate-transparency-go v1.1.4/go.mod h1:D6lvbfwckhNrbM9WVl1EVeMOyzC19mpIjMOI4nxBHtQ=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -119,6 +123,7 @@ github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
@@ -131,6 +136,7 @@ github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVt
|
||||
github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmoiron/sqlx v1.3.3 h1:j82X0bf7oQ27XeqxicSZsTU5suPwKElg3oyxNn43iTk=
|
||||
github.com/jmoiron/sqlx v1.3.3/go.mod h1:2BljVx/86SuTyjE+aPYlHCTNvZrnJXghYGpNiXLBMCQ=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVEN/8IXZe5Ooef3LQePvuBm9UWj6ZL8U=
|
||||
@@ -144,6 +150,7 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxv
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -229,6 +236,7 @@ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3c
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
@@ -256,11 +264,13 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a h1:tlJ7tGUHvcvL1v3yR6NcCc9nOqh2L+CG6HWrYQtwzQ0=
|
||||
github.com/theupdateframework/notary v0.7.1-0.20210315103452-bf96a202a09a/go.mod h1:Y94A6rPp2OwNfP/7vmf8O2xx2IykP8pPXQ1DLouGnEw=
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20200727161949-f79598599c5d h1:wvQZpqy8p0D/FUia6ipKDhXrzPzBVJE4PZyPc5+5Ay0=
|
||||
github.com/tonistiigi/go-rosetta v0.0.0-20200727161949-f79598599c5d/go.mod h1:xKQhd7snlzKFuUi1taTGWjpRE8iFTA06DeacYi3CVFQ=
|
||||
github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b h1:FsyNrX12e5BkplJq7wKOLk0+C6LZ+KGXvuEcKUYm5ss=
|
||||
github.com/weppos/publicsuffix-go v0.15.1-0.20210511084619-b1f36a2d6c0b/go.mod h1:HYux0V0Zi04bHNwOHy4cXJVz/TQjYonnF6aoYhj+3QE=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
@@ -271,7 +281,9 @@ github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQ
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc h1:zkGwegkOW709y0oiAraH/3D8njopUR/pARHv4tZZ6pw=
|
||||
github.com/zmap/zcrypto v0.0.0-20210511125630-18f1e0152cfc/go.mod h1:FM4U1E3NzlNMRnSUTU3P1UdukWhYGifqEsjk9fn7BCk=
|
||||
github.com/zmap/zlint/v3 v3.1.0 h1:WjVytZo79m/L1+/Mlphl09WBob6YTGljN5IGWZFpAv0=
|
||||
github.com/zmap/zlint/v3 v3.1.0/go.mod h1:L7t8s3sEKkb0A2BxGy1IWrxt1ZATa1R4QfJZaQOD3zU=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.6/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ=
|
||||
go.etcd.io/etcd/raft/v3 v3.5.6 h1:tOmx6Ym6rn2GpZOrvTGJZciJHek6RnC3U/zNInzIN50=
|
||||
go.etcd.io/etcd/raft/v3 v3.5.6/go.mod h1:wL8kkRGx1Hp8FmZUuHfL3K2/OaGIDaXGr1N7i2G07J0=
|
||||
@@ -280,13 +292,17 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:
|
||||
go.opentelemetry.io/otel v1.21.0 h1:hzLeKBZEL7Okw2mGzZ0cc4k/A7Fta0uoPgaJCr8fsFc=
|
||||
go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||
go.opentelemetry.io/otel/metric v1.21.0 h1:tlYWfeo+Bocx5kLEloTjbcDwBuELRrIFxwdQ36PlJu4=
|
||||
go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
|
||||
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
|
||||
go.opentelemetry.io/otel/trace v1.21.0 h1:WD9i5gzvoUPuXIXH24ZNBudiarZDKuekPqi/E8fpfLc=
|
||||
go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
@@ -297,8 +313,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
|
||||
@@ -310,8 +326,8 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -333,11 +349,11 @@ golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
|
||||
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
|
||||
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
|
||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
@@ -357,6 +373,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a h1:fwgW9j3vHirt4ObdHoYNwuO24BEZjSzbh+zPaNWoiY8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc=
|
||||
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
@@ -364,8 +381,8 @@ google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
|
||||
google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII=
|
||||
@@ -373,6 +390,7 @@ gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM=
|
||||
@@ -391,5 +409,6 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||
k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw=
|
||||
k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
|
||||
tags.cncf.io/container-device-interface v0.6.2 h1:dThE6dtp/93ZDGhqaED2Pu374SOeUkBfuvkLuiTdwzg=
|
||||
tags.cncf.io/container-device-interface v0.6.2/go.mod h1:Shusyhjs1A5Na/kqPVLL0KqnHQHuunol9LFeUNkuGVE=
|
||||
|
||||
+12
-10
@@ -391,7 +391,11 @@ definitions:
|
||||
ReadOnlyNonRecursive:
|
||||
description: |
|
||||
Make the mount non-recursively read-only, but still leave the mount recursive
|
||||
(unless NonRecursive is set to true in conjunction).
|
||||
(unless NonRecursive is set to `true` in conjunction).
|
||||
|
||||
Addded in v1.44, before that version all read-only mounts were
|
||||
non-recursive by default. To match the previous behaviour this
|
||||
will default to `true` for clients on versions prior to v1.44.
|
||||
type: "boolean"
|
||||
default: false
|
||||
ReadOnlyForceRecursive:
|
||||
@@ -1746,9 +1750,13 @@ definitions:
|
||||
Created:
|
||||
description: |
|
||||
Date and time at which the image was created, formatted in
|
||||
[RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds, or empty if the field was not set in the image config.
|
||||
[RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format with nano-seconds.
|
||||
|
||||
This information is only available if present in the image,
|
||||
and omitted otherwise.
|
||||
type: "string"
|
||||
x-nullable: false
|
||||
format: "dateTime"
|
||||
x-nullable: true
|
||||
example: "2022-02-04T21:20:12.497794809Z"
|
||||
Container:
|
||||
description: |
|
||||
@@ -8766,8 +8774,7 @@ paths:
|
||||
|
||||
<p><br /></p>
|
||||
|
||||
> **Deprecated**: This field is deprecated and will always
|
||||
> be "false" in future.
|
||||
> **Deprecated**: This field is deprecated and will always be "false".
|
||||
type: "boolean"
|
||||
example: false
|
||||
name:
|
||||
@@ -8810,13 +8817,8 @@ paths:
|
||||
description: |
|
||||
A JSON encoded value of the filters (a `map[string][]string`) to process on the images list. Available filters:
|
||||
|
||||
- `is-automated=(true|false)` (deprecated, see below)
|
||||
- `is-official=(true|false)`
|
||||
- `stars=<number>` Matches images that has at least 'number' stars.
|
||||
|
||||
The `is-automated` filter is deprecated. The `is_automated` field has
|
||||
been deprecated by Docker Hub's search API. Consequently, searching
|
||||
for `is-automated=true` will yield no results.
|
||||
type: "string"
|
||||
tags: ["Image"]
|
||||
/images/prune:
|
||||
|
||||
+1
-1
@@ -94,7 +94,7 @@ type SearchResult struct {
|
||||
Name string `json:"name"`
|
||||
// IsAutomated indicates whether the result is automated.
|
||||
//
|
||||
// Deprecated: the "is_automated" field is deprecated and will always be "false" in the future.
|
||||
// Deprecated: the "is_automated" field is deprecated and will always be "false".
|
||||
IsAutomated bool `json:"is_automated"`
|
||||
// Description is a textual description of the repository
|
||||
Description string `json:"description"`
|
||||
|
||||
+6
-3
@@ -72,14 +72,17 @@ type ImageInspect struct {
|
||||
|
||||
// Created is the date and time at which the image was created, formatted in
|
||||
// RFC 3339 nano-seconds (time.RFC3339Nano).
|
||||
Created string
|
||||
//
|
||||
// This information is only available if present in the image,
|
||||
// and omitted otherwise.
|
||||
Created string `json:",omitempty"`
|
||||
|
||||
// Container is the ID of the container that was used to create the image.
|
||||
//
|
||||
// Depending on how the image was created, this field may be empty.
|
||||
//
|
||||
// Deprecated: this field is omitted in API v1.45, but kept for backward compatibility.
|
||||
Container string
|
||||
Container string `json:",omitempty"`
|
||||
|
||||
// ContainerConfig is an optional field containing the configuration of the
|
||||
// container that was last committed when creating the image.
|
||||
@@ -88,7 +91,7 @@ type ImageInspect struct {
|
||||
// and it is not in active use anymore.
|
||||
//
|
||||
// Deprecated: this field is omitted in API v1.45, but kept for backward compatibility.
|
||||
ContainerConfig *container.Config
|
||||
ContainerConfig *container.Config `json:",omitempty"`
|
||||
|
||||
// DockerVersion is the version of Docker that was used to build the image.
|
||||
//
|
||||
|
||||
+5
-5
@@ -238,13 +238,13 @@ type TopologyRequirement struct {
|
||||
// If requisite is specified, all topologies in preferred list MUST
|
||||
// also be present in the list of requisite topologies.
|
||||
//
|
||||
// If the SP is unable to to make the provisioned volume available
|
||||
// If the SP is unable to make the provisioned volume available
|
||||
// from any of the preferred topologies, the SP MAY choose a topology
|
||||
// from the list of requisite topologies.
|
||||
// If the list of requisite topologies is not specified, then the SP
|
||||
// MAY choose from the list of all possible topologies.
|
||||
// If the list of requisite topologies is specified and the SP is
|
||||
// unable to to make the provisioned volume available from any of the
|
||||
// unable to make the provisioned volume available from any of the
|
||||
// requisite topologies it MUST fail the CreateVolume call.
|
||||
//
|
||||
// Example 1:
|
||||
@@ -254,7 +254,7 @@ type TopologyRequirement struct {
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
// preferred =
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
// then the the SP SHOULD first attempt to make the provisioned volume
|
||||
// then the SP SHOULD first attempt to make the provisioned volume
|
||||
// available from "zone" "Z3" in the "region" "R1" and fall back to
|
||||
// "zone" "Z2" in the "region" "R1" if that is not possible.
|
||||
//
|
||||
@@ -268,7 +268,7 @@ type TopologyRequirement struct {
|
||||
// preferred =
|
||||
// {"region": "R1", "zone": "Z4"},
|
||||
// {"region": "R1", "zone": "Z2"}
|
||||
// then the the SP SHOULD first attempt to make the provisioned volume
|
||||
// then the SP SHOULD first attempt to make the provisioned volume
|
||||
// accessible from "zone" "Z4" in the "region" "R1" and fall back to
|
||||
// "zone" "Z2" in the "region" "R1" if that is not possible. If that
|
||||
// is not possible, the SP may choose between either the "zone"
|
||||
@@ -287,7 +287,7 @@ type TopologyRequirement struct {
|
||||
// preferred =
|
||||
// {"region": "R1", "zone": "Z5"},
|
||||
// {"region": "R1", "zone": "Z3"}
|
||||
// then the the SP SHOULD first attempt to make the provisioned volume
|
||||
// then the SP SHOULD first attempt to make the provisioned volume
|
||||
// accessible from the combination of the two "zones" "Z5" and "Z3" in
|
||||
// the "region" "R1". If that's not possible, it should fall back to
|
||||
// a combination of "Z5" and other possibilities from the list of
|
||||
|
||||
+14
-5
@@ -265,17 +265,22 @@ func (cli *Client) Close() error {
|
||||
// This allows for version-dependent code to use the same version as will
|
||||
// be negotiated when making the actual requests, and for which cases
|
||||
// we cannot do the negotiation lazily.
|
||||
func (cli *Client) checkVersion(ctx context.Context) {
|
||||
if cli.negotiateVersion && !cli.negotiated {
|
||||
cli.NegotiateAPIVersion(ctx)
|
||||
func (cli *Client) checkVersion(ctx context.Context) error {
|
||||
if !cli.manualOverride && cli.negotiateVersion && !cli.negotiated {
|
||||
ping, err := cli.Ping(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cli.negotiateAPIVersionPing(ping)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAPIPath returns the versioned request path to call the API.
|
||||
// It appends the query parameters to the path if they are not empty.
|
||||
func (cli *Client) getAPIPath(ctx context.Context, p string, query url.Values) string {
|
||||
var apiPath string
|
||||
cli.checkVersion(ctx)
|
||||
_ = cli.checkVersion(ctx)
|
||||
if cli.version != "" {
|
||||
v := strings.TrimPrefix(cli.version, "v")
|
||||
apiPath = path.Join(cli.basePath, "/v"+v, p)
|
||||
@@ -307,7 +312,11 @@ func (cli *Client) ClientVersion() string {
|
||||
// added (1.24).
|
||||
func (cli *Client) NegotiateAPIVersion(ctx context.Context) {
|
||||
if !cli.manualOverride {
|
||||
ping, _ := cli.Ping(ctx)
|
||||
ping, err := cli.Ping(ctx)
|
||||
if err != nil {
|
||||
// FIXME(thaJeztah): Ping returns an error when failing to connect to the API; we should not swallow the error here, and instead returning it.
|
||||
return
|
||||
}
|
||||
cli.negotiateAPIVersionPing(ping)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -28,7 +28,9 @@ func (cli *Client) ContainerCreate(ctx context.Context, config *container.Config
|
||||
//
|
||||
// Normally, version-negotiation (if enabled) would not happen until
|
||||
// the API request is made.
|
||||
cli.checkVersion(ctx)
|
||||
if err := cli.checkVersion(ctx); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
if err := cli.NewVersionError(ctx, "1.25", "stop timeout"); config != nil && config.StopTimeout != nil && err != nil {
|
||||
return response, err
|
||||
|
||||
+3
-1
@@ -18,7 +18,9 @@ func (cli *Client) ContainerExecCreate(ctx context.Context, container string, co
|
||||
//
|
||||
// Normally, version-negotiation (if enabled) would not happen until
|
||||
// the API request is made.
|
||||
cli.checkVersion(ctx)
|
||||
if err := cli.checkVersion(ctx); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
if err := cli.NewVersionError(ctx, "1.25", "env"); len(config.Env) != 0 && err != nil {
|
||||
return response, err
|
||||
|
||||
+3
-1
@@ -23,7 +23,9 @@ func (cli *Client) ContainerRestart(ctx context.Context, containerID string, opt
|
||||
//
|
||||
// Normally, version-negotiation (if enabled) would not happen until
|
||||
// the API request is made.
|
||||
cli.checkVersion(ctx)
|
||||
if err := cli.checkVersion(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if versions.GreaterThanOrEqualTo(cli.version, "1.42") {
|
||||
query.Set("signal", options.Signal)
|
||||
}
|
||||
|
||||
+3
-1
@@ -27,7 +27,9 @@ func (cli *Client) ContainerStop(ctx context.Context, containerID string, option
|
||||
//
|
||||
// Normally, version-negotiation (if enabled) would not happen until
|
||||
// the API request is made.
|
||||
cli.checkVersion(ctx)
|
||||
if err := cli.checkVersion(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if versions.GreaterThanOrEqualTo(cli.version, "1.42") {
|
||||
query.Set("signal", options.Signal)
|
||||
}
|
||||
|
||||
+7
-4
@@ -30,19 +30,22 @@ const containerWaitErrorMsgLimit = 2 * 1024 /* Max: 2KiB */
|
||||
// synchronize ContainerWait with other calls, such as specifying a
|
||||
// "next-exit" condition before issuing a ContainerStart request.
|
||||
func (cli *Client) ContainerWait(ctx context.Context, containerID string, condition container.WaitCondition) (<-chan container.WaitResponse, <-chan error) {
|
||||
resultC := make(chan container.WaitResponse)
|
||||
errC := make(chan error, 1)
|
||||
|
||||
// Make sure we negotiated (if the client is configured to do so),
|
||||
// as code below contains API-version specific handling of options.
|
||||
//
|
||||
// Normally, version-negotiation (if enabled) would not happen until
|
||||
// the API request is made.
|
||||
cli.checkVersion(ctx)
|
||||
if err := cli.checkVersion(ctx); err != nil {
|
||||
errC <- err
|
||||
return resultC, errC
|
||||
}
|
||||
if versions.LessThan(cli.ClientVersion(), "1.30") {
|
||||
return cli.legacyContainerWait(ctx, containerID)
|
||||
}
|
||||
|
||||
resultC := make(chan container.WaitResponse)
|
||||
errC := make(chan error, 1)
|
||||
|
||||
query := url.Values{}
|
||||
if condition != "" {
|
||||
query.Set("condition", string(condition))
|
||||
|
||||
+17
-8
@@ -11,15 +11,16 @@ import (
|
||||
|
||||
// errConnectionFailed implements an error returned when connection failed.
|
||||
type errConnectionFailed struct {
|
||||
host string
|
||||
error
|
||||
}
|
||||
|
||||
// Error returns a string representation of an errConnectionFailed
|
||||
func (err errConnectionFailed) Error() string {
|
||||
if err.host == "" {
|
||||
return "Cannot connect to the Docker daemon. Is the docker daemon running on this host?"
|
||||
}
|
||||
return fmt.Sprintf("Cannot connect to the Docker daemon at %s. Is the docker daemon running?", err.host)
|
||||
func (e errConnectionFailed) Error() string {
|
||||
return e.error.Error()
|
||||
}
|
||||
|
||||
func (e errConnectionFailed) Unwrap() error {
|
||||
return e.error
|
||||
}
|
||||
|
||||
// IsErrConnectionFailed returns true if the error is caused by connection failed.
|
||||
@@ -29,7 +30,13 @@ func IsErrConnectionFailed(err error) bool {
|
||||
|
||||
// ErrorConnectionFailed returns an error with host in the error message when connection to docker daemon failed.
|
||||
func ErrorConnectionFailed(host string) error {
|
||||
return errConnectionFailed{host: host}
|
||||
var err error
|
||||
if host == "" {
|
||||
err = fmt.Errorf("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
|
||||
} else {
|
||||
err = fmt.Errorf("Cannot connect to the Docker daemon at %s. Is the docker daemon running?", host)
|
||||
}
|
||||
return errConnectionFailed{error: err}
|
||||
}
|
||||
|
||||
// IsErrNotFound returns true if the error is a NotFound error, which is returned
|
||||
@@ -60,7 +67,9 @@ func (cli *Client) NewVersionError(ctx context.Context, APIrequired, feature str
|
||||
//
|
||||
// Normally, version-negotiation (if enabled) would not happen until
|
||||
// the API request is made.
|
||||
cli.checkVersion(ctx)
|
||||
if err := cli.checkVersion(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if cli.version != "" && versions.LessThan(cli.version, APIrequired) {
|
||||
return fmt.Errorf("%q requires API version %s, but the Docker daemon API version is %s", feature, APIrequired, cli.version)
|
||||
}
|
||||
|
||||
+5
-2
@@ -12,14 +12,17 @@ import (
|
||||
|
||||
// ImageList returns a list of images in the docker host.
|
||||
func (cli *Client) ImageList(ctx context.Context, options image.ListOptions) ([]image.Summary, error) {
|
||||
var images []image.Summary
|
||||
|
||||
// Make sure we negotiated (if the client is configured to do so),
|
||||
// as code below contains API-version specific handling of options.
|
||||
//
|
||||
// Normally, version-negotiation (if enabled) would not happen until
|
||||
// the API request is made.
|
||||
cli.checkVersion(ctx)
|
||||
if err := cli.checkVersion(ctx); err != nil {
|
||||
return images, err
|
||||
}
|
||||
|
||||
var images []image.Summary
|
||||
query := url.Values{}
|
||||
|
||||
optionFilters := options.Filters
|
||||
|
||||
+5
-2
@@ -10,12 +10,16 @@ import (
|
||||
|
||||
// NetworkCreate creates a new network in the docker host.
|
||||
func (cli *Client) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
|
||||
var response types.NetworkCreateResponse
|
||||
|
||||
// Make sure we negotiated (if the client is configured to do so),
|
||||
// as code below contains API-version specific handling of options.
|
||||
//
|
||||
// Normally, version-negotiation (if enabled) would not happen until
|
||||
// the API request is made.
|
||||
cli.checkVersion(ctx)
|
||||
if err := cli.checkVersion(ctx); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
networkCreateRequest := types.NetworkCreateRequest{
|
||||
NetworkCreate: options,
|
||||
@@ -25,7 +29,6 @@ func (cli *Client) NetworkCreate(ctx context.Context, name string, options types
|
||||
networkCreateRequest.CheckDuplicate = true //nolint:staticcheck // ignore SA1019: CheckDuplicate is deprecated since API v1.44.
|
||||
}
|
||||
|
||||
var response types.NetworkCreateResponse
|
||||
serverResp, err := cli.post(ctx, "/networks/create", nil, networkCreateRequest, nil)
|
||||
defer ensureReaderClosed(serverResp)
|
||||
if err != nil {
|
||||
|
||||
+4
-1
@@ -14,7 +14,10 @@ import (
|
||||
// Ping pings the server and returns the value of the "Docker-Experimental",
|
||||
// "Builder-Version", "OS-Type" & "API-Version" headers. It attempts to use
|
||||
// a HEAD request on the endpoint, but falls back to GET if HEAD is not supported
|
||||
// by the daemon.
|
||||
// by the daemon. It ignores internal server errors returned by the API, which
|
||||
// may be returned if the daemon is in an unhealthy state, but returns errors
|
||||
// for other non-success status codes, failing to connect to the API, or failing
|
||||
// to parse the API response.
|
||||
func (cli *Client) Ping(ctx context.Context) (types.Ping, error) {
|
||||
var ping types.Ping
|
||||
|
||||
|
||||
+6
-4
@@ -134,17 +134,18 @@ func (cli *Client) sendRequest(ctx context.Context, method, path string, query u
|
||||
return resp, errdefs.FromStatusCode(err, resp.statusCode)
|
||||
}
|
||||
|
||||
// FIXME(thaJeztah): Should this actually return a serverResp when a connection error occurred?
|
||||
func (cli *Client) doRequest(req *http.Request) (serverResponse, error) {
|
||||
serverResp := serverResponse{statusCode: -1, reqURL: req.URL}
|
||||
|
||||
resp, err := cli.client.Do(req)
|
||||
if err != nil {
|
||||
if cli.scheme != "https" && strings.Contains(err.Error(), "malformed HTTP response") {
|
||||
return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
||||
return serverResp, errConnectionFailed{fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)}
|
||||
}
|
||||
|
||||
if cli.scheme == "https" && strings.Contains(err.Error(), "bad certificate") {
|
||||
return serverResp, errors.Wrap(err, "the server probably has client authentication (--tlsverify) enabled; check your TLS client certification settings")
|
||||
return serverResp, errConnectionFailed{errors.Wrap(err, "the server probably has client authentication (--tlsverify) enabled; check your TLS client certification settings")}
|
||||
}
|
||||
|
||||
// Don't decorate context sentinel errors; users may be comparing to
|
||||
@@ -156,12 +157,13 @@ func (cli *Client) doRequest(req *http.Request) (serverResponse, error) {
|
||||
if uErr, ok := err.(*url.Error); ok {
|
||||
if nErr, ok := uErr.Err.(*net.OpError); ok {
|
||||
if os.IsPermission(nErr.Err) {
|
||||
return serverResp, errors.Wrapf(err, "permission denied while trying to connect to the Docker daemon socket at %v", cli.host)
|
||||
return serverResp, errConnectionFailed{errors.Wrapf(err, "permission denied while trying to connect to the Docker daemon socket at %v", cli.host)}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if nErr, ok := err.(net.Error); ok {
|
||||
// FIXME(thaJeztah): any net.Error should be considered a connection error (but we should include the original error)?
|
||||
if nErr.Timeout() {
|
||||
return serverResp, ErrorConnectionFailed(cli.host)
|
||||
}
|
||||
@@ -190,7 +192,7 @@ func (cli *Client) doRequest(req *http.Request) (serverResponse, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return serverResp, errors.Wrap(err, "error during connect")
|
||||
return serverResp, errConnectionFailed{errors.Wrap(err, "error during connect")}
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
|
||||
+3
-1
@@ -25,7 +25,9 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
|
||||
//
|
||||
// Normally, version-negotiation (if enabled) would not happen until
|
||||
// the API request is made.
|
||||
cli.checkVersion(ctx)
|
||||
if err := cli.checkVersion(ctx); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
// Make sure containerSpec is not nil when no runtime is set or the runtime is set to container
|
||||
if service.TaskTemplate.ContainerSpec == nil && (service.TaskTemplate.Runtime == "" || service.TaskTemplate.Runtime == swarm.RuntimeContainer) {
|
||||
|
||||
+6
-6
@@ -16,18 +16,18 @@ import (
|
||||
// It should be the value as set *before* the update. You can find this value in the Meta field
|
||||
// of swarm.Service, which can be found using ServiceInspectWithRaw.
|
||||
func (cli *Client) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (swarm.ServiceUpdateResponse, error) {
|
||||
response := swarm.ServiceUpdateResponse{}
|
||||
|
||||
// Make sure we negotiated (if the client is configured to do so),
|
||||
// as code below contains API-version specific handling of options.
|
||||
//
|
||||
// Normally, version-negotiation (if enabled) would not happen until
|
||||
// the API request is made.
|
||||
cli.checkVersion(ctx)
|
||||
|
||||
var (
|
||||
query = url.Values{}
|
||||
response = swarm.ServiceUpdateResponse{}
|
||||
)
|
||||
if err := cli.checkVersion(ctx); err != nil {
|
||||
return response, err
|
||||
}
|
||||
|
||||
query := url.Values{}
|
||||
if options.RegistryAuthFrom != "" {
|
||||
query.Set("registryAuthFrom", options.RegistryAuthFrom)
|
||||
}
|
||||
|
||||
+3
-1
@@ -16,7 +16,9 @@ func (cli *Client) VolumeRemove(ctx context.Context, volumeID string, force bool
|
||||
//
|
||||
// Normally, version-negotiation (if enabled) would not happen until
|
||||
// the API request is made.
|
||||
cli.checkVersion(ctx)
|
||||
if err := cli.checkVersion(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
if versions.GreaterThanOrEqualTo(cli.version, "1.25") {
|
||||
query.Set("force", "1")
|
||||
}
|
||||
|
||||
+10
-7
@@ -27,11 +27,16 @@ func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term s
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO(thaJeztah): the "is-automated" field is deprecated; reset the field for the next release (v26.0.0). Return early when using "is-automated=true", and ignore "is-automated=false".
|
||||
isAutomated, err := searchFilters.GetBoolOrDefault("is-automated", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// "is-automated" is deprecated and filtering for `true` will yield no results.
|
||||
if isAutomated {
|
||||
return []registry.SearchResult{}, nil
|
||||
}
|
||||
|
||||
isOfficial, err := searchFilters.GetBoolOrDefault("is-official", false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -51,7 +56,6 @@ func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term s
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(thaJeztah): the "is-automated" field is deprecated. Reset the field for the next release (v26.0.0) if any "true" values are present.
|
||||
unfilteredResult, err := s.searchUnfiltered(ctx, term, limit, authConfig, headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -59,11 +63,6 @@ func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term s
|
||||
|
||||
filteredResults := []registry.SearchResult{}
|
||||
for _, result := range unfilteredResult.Results {
|
||||
if searchFilters.Contains("is-automated") {
|
||||
if isAutomated != result.IsAutomated { //nolint:staticcheck // ignore SA1019 for old API versions.
|
||||
continue
|
||||
}
|
||||
}
|
||||
if searchFilters.Contains("is-official") {
|
||||
if isOfficial != result.IsOfficial {
|
||||
continue
|
||||
@@ -74,6 +73,10 @@ func (s *Service) Search(ctx context.Context, searchFilters filters.Args, term s
|
||||
continue
|
||||
}
|
||||
}
|
||||
// "is-automated" is deprecated and the value in Docker Hub search
|
||||
// results is untrustworthy. Force it to false so as to not mislead our
|
||||
// clients.
|
||||
result.IsAutomated = false //nolint:staticcheck // ignore SA1019 (field is deprecated)
|
||||
filteredResults = append(filteredResults, result)
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -56,6 +56,7 @@ type Unmarshaler struct {
|
||||
// implement JSONPBMarshaler so that the custom format can be produced.
|
||||
//
|
||||
// The JSON unmarshaling must follow the JSON to proto specification:
|
||||
//
|
||||
// https://developers.google.com/protocol-buffers/docs/proto3#json
|
||||
//
|
||||
// Deprecated: Custom types should implement protobuf reflection instead.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user