Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 30df2d0b3a | |||
| ecc694264d | |||
| c475c696cf | |||
| 7494d2cee4 | |||
| 5306df36fa | |||
| 5dbaa52325 | |||
| dd832b6d97 | |||
| a99e91cc89 | |||
| 579b72aa06 | |||
| 6fc5891770 | |||
| 9af6cbc489 | |||
| ba53322412 | |||
| 61d2e8aaad | |||
| 6abcd4a2a1 | |||
| 00c129b974 | |||
| fbf5c8a86d | |||
| 4c7d52534f | |||
| 70123c523c | |||
| 4942b2747f | |||
| d8bab71747 | |||
| 36d9523e31 | |||
| 7063a0ef07 | |||
| 22487ad6f6 | |||
| 4737ed4906 | |||
| 8cff0087da | |||
| e8b22ce03c | |||
| e180ab8ab8 | |||
| 0d799c556f | |||
| 887030fbe8 | |||
| 9c6a0e0ba9 | |||
| f784471104 | |||
| d7afcf9b98 | |||
| 9d9adf6346 | |||
| d4b7734f18 | |||
| a1061611fd | |||
| 5e42f826b4 | |||
| 645c23bd13 | |||
| e491078fc6 | |||
| a3ffb8a148 | |||
| a4ae5f2f7a | |||
| 394991e2ab | |||
| e5bce5cd2d | |||
| d5c181abf4 | |||
| 0351ece9e5 | |||
| ec00b85794 | |||
| a69c591c5a | |||
| f9d2820a20 | |||
| c28ec0e4ce | |||
| d1c8336395 | |||
| 61b9fd4068 | |||
| 2ef1b4eabe | |||
| bea31fccbb | |||
| e9c189e1c2 | |||
| 118548d02b | |||
| 026ef0df2d | |||
| 4665091715 | |||
| 261d8bcf8d | |||
| 3755161455 | |||
| db5a0ae673 | |||
| 843153da37 | |||
| a2d7989230 | |||
| 985cee2de0 | |||
| e1dd0e1501 | |||
| 82ff4b5634 | |||
| 4de56bc72f | |||
| 2ed0d99acc | |||
| cbeddb1390 | |||
| 1fb1577626 | |||
| 67885d0dcc | |||
| 2ed42a8ade | |||
| 4bac500fb2 | |||
| 9d9f632527 | |||
| a2e17eb9d5 | |||
| 5d201ca436 | |||
| d1122a2293 | |||
| d52de77ef4 | |||
| 01f8949484 | |||
| b9e3346b1e | |||
| 25421ace0b | |||
| ede7019b14 | |||
| d48256b26d | |||
| 09fcf8e3dd | |||
| 56f7bd0759 | |||
| 3bacc99580 | |||
| f77defa891 | |||
| 4a677b86a6 | |||
| 1cf78c49ab | |||
| 2fb1298416 | |||
| a0ebf3e35c | |||
| 428518a84a | |||
| c1914f73f5 | |||
| cab5014d57 | |||
| 7c3ec3ec02 | |||
| aa976192cb | |||
| 2de1ea9769 | |||
| 0ac676b171 | |||
| b627f18262 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -88,7 +88,7 @@ jobs:
|
||||
fi
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
path: /tmp/out/*
|
||||
|
||||
10
.github/workflows/codeql.yml
vendored
10
.github/workflows/codeql.yml
vendored
@ -61,19 +61,19 @@ jobs:
|
||||
ln -s vendor.sum go.sum
|
||||
-
|
||||
name: Update Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.24.7"
|
||||
go-version: "1.24.9"
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@v4
|
||||
with:
|
||||
languages: go
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@v4
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@v4
|
||||
with:
|
||||
category: "/language:go"
|
||||
|
||||
3
.github/workflows/e2e.yml
vendored
3
.github/workflows/e2e.yml
vendored
@ -39,8 +39,7 @@ jobs:
|
||||
engine-version:
|
||||
- 28 # latest
|
||||
- 27 # latest - 1
|
||||
- 26 # github actions default
|
||||
- 23 # mirantis lts
|
||||
- 25 # mirantis lts
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
|
||||
9
.github/workflows/test.yml
vendored
9
.github/workflows/test.yml
vendored
@ -53,8 +53,9 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- macos-13 # macOS 13 on Intel
|
||||
- macos-14 # macOS 14 on arm64 (Apple Silicon M1)
|
||||
- macos-14 # macOS 14 on arm64 (Apple Silicon M1)
|
||||
- macos-15-intel # macOS 15 on Intel
|
||||
- macos-15 # macOS 15 on arm64 (Apple Silicon M1)
|
||||
# - windows-2022 # FIXME: some tests are failing on the Windows runner, as well as on Appveyor since June 24, 2018: https://ci.appveyor.com/project/docker/cli/history
|
||||
steps:
|
||||
-
|
||||
@ -64,9 +65,9 @@ jobs:
|
||||
path: ${{ env.GOPATH }}/src/github.com/docker/cli
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: "1.24.7"
|
||||
go-version: "1.24.9"
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
|
||||
@ -5,7 +5,7 @@ run:
|
||||
# which causes it to fallback to go1.17 semantics.
|
||||
#
|
||||
# TODO(thaJeztah): update "usetesting" settings to enable go1.24 features once our minimum version is go1.24
|
||||
go: "1.24.7"
|
||||
go: "1.24.9"
|
||||
|
||||
timeout: 5m
|
||||
|
||||
|
||||
@ -8,13 +8,13 @@ ARG BASE_VARIANT=alpine
|
||||
ARG ALPINE_VERSION=3.21
|
||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||
|
||||
ARG GO_VERSION=1.24.7
|
||||
ARG GO_VERSION=1.24.9
|
||||
ARG XX_VERSION=1.6.1
|
||||
ARG GOVERSIONINFO_VERSION=v1.4.1
|
||||
|
||||
# GOTESTSUM_VERSION sets the version of gotestsum to install in the dev container.
|
||||
# It must be a valid tag in the https://github.com/gotestyourself/gotestsum repository.
|
||||
ARG GOTESTSUM_VERSION=v1.12.3
|
||||
ARG GOTESTSUM_VERSION=v1.13.0
|
||||
|
||||
# BUILDX_VERSION sets the version of buildx to use for the e2e tests.
|
||||
# It must be a tag in the docker.io/docker/buildx-bin image repository
|
||||
|
||||
@ -32,14 +32,12 @@ func (c *fakeCandidate) Metadata() ([]byte, error) {
|
||||
func TestValidateCandidate(t *testing.T) {
|
||||
const (
|
||||
goodPluginName = metadata.NamePrefix + "goodplugin"
|
||||
builtinName = metadata.NamePrefix + "builtin"
|
||||
builtinAlias = metadata.NamePrefix + "alias"
|
||||
|
||||
builtinName = metadata.NamePrefix + "builtin"
|
||||
builtinAlias = metadata.NamePrefix + "alias"
|
||||
|
||||
badPrefixPath = "/usr/local/libexec/cli-plugins/wobble"
|
||||
badNamePath = "/usr/local/libexec/cli-plugins/docker-123456"
|
||||
goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName
|
||||
metaExperimental = `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`
|
||||
badPrefixPath = "/usr/local/libexec/cli-plugins/wobble"
|
||||
badNamePath = "/usr/local/libexec/cli-plugins/docker-123456"
|
||||
goodPluginPath = "/usr/local/libexec/cli-plugins/" + goodPluginName
|
||||
)
|
||||
|
||||
fakeroot := &cobra.Command{Use: "docker"}
|
||||
@ -51,31 +49,103 @@ func TestValidateCandidate(t *testing.T) {
|
||||
})
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
c *fakeCandidate
|
||||
name string
|
||||
plugin *fakeCandidate
|
||||
|
||||
// Either err or invalid may be non-empty, but not both (both can be empty for a good plugin).
|
||||
err string
|
||||
invalid string
|
||||
expVer string
|
||||
}{
|
||||
/* Each failing one of the tests */
|
||||
{name: "empty path", c: &fakeCandidate{path: ""}, err: "plugin candidate path cannot be empty"},
|
||||
{name: "bad prefix", c: &fakeCandidate{path: badPrefixPath}, err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix)},
|
||||
{name: "bad path", c: &fakeCandidate{path: badNamePath}, invalid: "did not match"},
|
||||
{name: "builtin command", c: &fakeCandidate{path: builtinName}, invalid: `plugin "builtin" duplicates builtin command`},
|
||||
{name: "builtin alias", c: &fakeCandidate{path: builtinAlias}, invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`},
|
||||
{name: "fetch failure", c: &fakeCandidate{path: goodPluginPath, exec: false}, invalid: fmt.Sprintf("failed to fetch metadata: faked a failure to exec %q", goodPluginPath)},
|
||||
{name: "metadata not json", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `xyzzy`}, invalid: "invalid character"},
|
||||
{name: "empty schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`}, invalid: `plugin SchemaVersion "" is not valid`},
|
||||
{name: "invalid schemaversion", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`}, invalid: `plugin SchemaVersion "xyzzy" is not valid`},
|
||||
{name: "no vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`}, invalid: "plugin metadata does not define a vendor"},
|
||||
{name: "empty vendor", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`}, invalid: "plugin metadata does not define a vendor"},
|
||||
// This one should work
|
||||
{name: "valid", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`}},
|
||||
{name: "experimental + allowing experimental", c: &fakeCandidate{path: goodPluginPath, exec: true, meta: metaExperimental}},
|
||||
// Invalid cases.
|
||||
{
|
||||
name: "empty path",
|
||||
plugin: &fakeCandidate{path: ""},
|
||||
err: "plugin candidate path cannot be empty",
|
||||
},
|
||||
{
|
||||
name: "bad prefix",
|
||||
plugin: &fakeCandidate{path: badPrefixPath},
|
||||
err: fmt.Sprintf("does not have %q prefix", metadata.NamePrefix),
|
||||
},
|
||||
{
|
||||
name: "bad path",
|
||||
plugin: &fakeCandidate{path: badNamePath},
|
||||
invalid: "did not match",
|
||||
},
|
||||
{
|
||||
name: "builtin command",
|
||||
plugin: &fakeCandidate{path: builtinName},
|
||||
invalid: `plugin "builtin" duplicates builtin command`,
|
||||
},
|
||||
{
|
||||
name: "builtin alias",
|
||||
plugin: &fakeCandidate{path: builtinAlias},
|
||||
invalid: `plugin "alias" duplicates an alias of builtin command "builtin"`,
|
||||
},
|
||||
{
|
||||
name: "fetch failure",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: false},
|
||||
invalid: fmt.Sprintf("failed to fetch metadata: faked a failure to exec %q", goodPluginPath),
|
||||
},
|
||||
{
|
||||
name: "metadata not json",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `xyzzy`},
|
||||
invalid: "invalid character",
|
||||
},
|
||||
{
|
||||
name: "empty schemaversion",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{}`},
|
||||
invalid: `plugin SchemaVersion version cannot be empty`,
|
||||
},
|
||||
{
|
||||
name: "invalid schemaversion",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "xyzzy"}`},
|
||||
invalid: `plugin SchemaVersion "xyzzy" has wrong format: must be <major>.<minor>.<patch>`,
|
||||
},
|
||||
{
|
||||
name: "invalid schemaversion major",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "2.0.0"}`},
|
||||
invalid: `plugin SchemaVersion "2.0.0" is not supported: must be lower than 2.0.0`,
|
||||
},
|
||||
{
|
||||
name: "no vendor",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0"}`},
|
||||
invalid: "plugin metadata does not define a vendor",
|
||||
},
|
||||
{
|
||||
name: "empty vendor",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": ""}`},
|
||||
invalid: "plugin metadata does not define a vendor",
|
||||
},
|
||||
|
||||
// Valid cases.
|
||||
{
|
||||
name: "valid",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing"}`},
|
||||
expVer: "0.1.0",
|
||||
},
|
||||
{
|
||||
// Including the deprecated "experimental" field should not break processing.
|
||||
name: "with legacy experimental",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.1.0", "Vendor": "e2e-testing", "Experimental": true}`},
|
||||
expVer: "0.1.0",
|
||||
},
|
||||
{
|
||||
// note that this may not be supported by older CLIs
|
||||
name: "new minor schema version",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "0.2.0", "Vendor": "e2e-testing"}`},
|
||||
expVer: "0.2.0",
|
||||
},
|
||||
{
|
||||
// note that this may not be supported by older CLIs
|
||||
name: "new major schema version",
|
||||
plugin: &fakeCandidate{path: goodPluginPath, exec: true, meta: `{"SchemaVersion": "1.0.0", "Vendor": "e2e-testing"}`},
|
||||
expVer: "1.0.0",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p, err := newPlugin(tc.c, fakeroot.Commands())
|
||||
p, err := newPlugin(tc.plugin, fakeroot.Commands())
|
||||
switch {
|
||||
case tc.err != "":
|
||||
assert.ErrorContains(t, err, tc.err)
|
||||
@ -86,7 +156,7 @@ func TestValidateCandidate(t *testing.T) {
|
||||
default:
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, metadata.NamePrefix+p.Name, goodPluginName)
|
||||
assert.Equal(t, p.SchemaVersion, "0.1.0")
|
||||
assert.Equal(t, p.SchemaVersion, tc.expVer)
|
||||
assert.Equal(t, p.Vendor, "e2e-testing")
|
||||
}
|
||||
})
|
||||
|
||||
@ -38,6 +38,7 @@ func AddPluginCommandStubs(dockerCLI config.Provider, rootCmd *cobra.Command) (e
|
||||
rootCmd.AddCommand(&cobra.Command{
|
||||
Use: p.Name,
|
||||
Short: p.ShortDescription,
|
||||
Hidden: p.Hidden,
|
||||
Run: func(_ *cobra.Command, _ []string) {},
|
||||
Annotations: annotations,
|
||||
DisableFlagParsing: true,
|
||||
|
||||
@ -2,6 +2,7 @@ package manager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@ -13,6 +14,7 @@ import (
|
||||
"github.com/docker/cli/cli-plugins/metadata"
|
||||
"github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/debug"
|
||||
"github.com/fvbommel/sortorder"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@ -74,9 +76,17 @@ func addPluginCandidatesFromDir(res map[string][]string, d string) {
|
||||
return
|
||||
}
|
||||
for _, dentry := range dentries {
|
||||
switch dentry.Type() & os.ModeType { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list
|
||||
case 0, os.ModeSymlink:
|
||||
// Regular file or symlink, keep going
|
||||
switch mode := dentry.Type() & os.ModeType; mode { //nolint:exhaustive,nolintlint // no need to include all possible file-modes in this list
|
||||
case os.ModeSymlink:
|
||||
if !debug.IsEnabled() {
|
||||
// Skip broken symlinks unless debug is enabled. With debug
|
||||
// enabled, this will print a warning in "docker info".
|
||||
if _, err := os.Stat(filepath.Join(d, dentry.Name())); errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
case 0:
|
||||
// Regular file, keep going
|
||||
default:
|
||||
// Something else, ignore.
|
||||
continue
|
||||
|
||||
@ -38,7 +38,7 @@ func TestListPluginCandidates(t *testing.T) {
|
||||
"plugins3-target", // Will be referenced as a symlink from below
|
||||
fs.WithFile("docker-plugin1", ""),
|
||||
fs.WithDir("ignored3"),
|
||||
fs.WithSymlink("docker-brokensymlink", "broken"), // A broken symlink is still a candidate (but would fail tests later)
|
||||
fs.WithSymlink("docker-brokensymlink", "broken"), // A broken symlink is ignored
|
||||
fs.WithFile("non-plugin-symlinked", ""), // This shouldn't appear, but ...
|
||||
fs.WithSymlink("docker-symlinked", "non-plugin-symlinked"), // ... this link to it should.
|
||||
),
|
||||
@ -72,9 +72,6 @@ func TestListPluginCandidates(t *testing.T) {
|
||||
"hardlink2": {
|
||||
dir.Join("plugins2", "docker-hardlink2"),
|
||||
},
|
||||
"brokensymlink": {
|
||||
dir.Join("plugins3", "docker-brokensymlink"),
|
||||
},
|
||||
"symlinked": {
|
||||
dir.Join("plugins3", "docker-symlinked"),
|
||||
},
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli-plugins/metadata"
|
||||
@ -118,8 +119,8 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
|
||||
p.Err = wrapAsPluginError(err, "invalid metadata")
|
||||
return p, nil
|
||||
}
|
||||
if p.Metadata.SchemaVersion != "0.1.0" {
|
||||
p.Err = newPluginError("plugin SchemaVersion %q is not valid, must be 0.1.0", p.Metadata.SchemaVersion)
|
||||
if err := validateSchemaVersion(p.Metadata.SchemaVersion); err != nil {
|
||||
p.Err = &pluginError{cause: err}
|
||||
return p, nil
|
||||
}
|
||||
if p.Metadata.Vendor == "" {
|
||||
@ -129,6 +130,31 @@ func newPlugin(c pluginCandidate, cmds []*cobra.Command) (Plugin, error) {
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// validateSchemaVersion validates if the plugin's schemaVersion is supported.
|
||||
//
|
||||
// The current schema-version is "0.1.0", but we don't want to break compatibility
|
||||
// until v2.0.0 of the schema version. Check for the major version to be < 2.0.0.
|
||||
//
|
||||
// Note that CLI versions before 28.4.1 may not support these versions as they were
|
||||
// hard-coded to only accept "0.1.0".
|
||||
func validateSchemaVersion(version string) error {
|
||||
if version == "0.1.0" {
|
||||
return nil
|
||||
}
|
||||
if version == "" {
|
||||
return errors.New("plugin SchemaVersion version cannot be empty")
|
||||
}
|
||||
major, _, ok := strings.Cut(version, ".")
|
||||
majorVersion, err := strconv.Atoi(major)
|
||||
if !ok || err != nil {
|
||||
return fmt.Errorf("plugin SchemaVersion %q has wrong format: must be <major>.<minor>.<patch>", version)
|
||||
}
|
||||
if majorVersion > 1 {
|
||||
return fmt.Errorf("plugin SchemaVersion %q is not supported: must be lower than 2.0.0", version)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunHook executes the plugin's hooks command
|
||||
// and returns its unprocessed output.
|
||||
func (p *Plugin) RunHook(ctx context.Context, hookData HookPluginData) ([]byte, error) {
|
||||
|
||||
@ -33,4 +33,6 @@ type Metadata struct {
|
||||
ShortDescription string `json:",omitempty"`
|
||||
// URL is a pointer to the plugin's homepage.
|
||||
URL string `json:",omitempty"`
|
||||
// Hidden hides the plugin in completion and help message output.
|
||||
Hidden bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
@ -80,19 +80,23 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta metadat
|
||||
return cmd.Execute()
|
||||
}
|
||||
|
||||
// Run is the top-level entry point to the CLI plugin framework. It should be called from your plugin's `main()` function.
|
||||
func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) {
|
||||
// Run is the top-level entry point to the CLI plugin framework. It should
|
||||
// be called from the plugin's "main()" function. It initializes a new
|
||||
// [command.DockerCli] instance with the given options before calling
|
||||
// makeCmd to construct the plugin command, then invokes the plugin command
|
||||
// using [RunPlugin].
|
||||
func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata, ops ...command.CLIOption) {
|
||||
otel.SetErrorHandler(debug.OTELErrorHandler)
|
||||
|
||||
dockerCli, err := command.NewDockerCli()
|
||||
dockerCLI, err := command.NewDockerCli(ops...)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
_, _ = fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
plugin := makeCmd(dockerCli)
|
||||
plugin := makeCmd(dockerCLI)
|
||||
|
||||
if err := RunPlugin(dockerCli, plugin, meta); err != nil {
|
||||
if err := RunPlugin(dockerCLI, plugin, meta); err != nil {
|
||||
var stErr cli.StatusError
|
||||
if errors.As(err, &stErr) {
|
||||
// StatusError should only be used for errors, and all errors should
|
||||
@ -100,10 +104,10 @@ func Run(makeCmd func(command.Cli) *cobra.Command, meta metadata.Metadata) {
|
||||
if stErr.StatusCode == 0 { // FIXME(thaJeztah): this should never be used with a zero status-code. Check if we do this anywhere.
|
||||
stErr.StatusCode = 1
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCli.Err(), stErr)
|
||||
_, _ = fmt.Fprintln(dockerCLI.Err(), stErr)
|
||||
os.Exit(stErr.StatusCode)
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCli.Err(), err)
|
||||
_, _ = fmt.Fprintln(dockerCLI.Err(), err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -376,13 +376,10 @@ func orchestratorSubCommands(cmd *cobra.Command) []*cobra.Command {
|
||||
func allManagementSubCommands(cmd *cobra.Command) []*cobra.Command {
|
||||
cmds := []*cobra.Command{}
|
||||
for _, sub := range cmd.Commands() {
|
||||
if isPlugin(sub) {
|
||||
if invalidPluginReason(sub) == "" {
|
||||
cmds = append(cmds, sub)
|
||||
}
|
||||
if invalidPluginReason(sub) != "" {
|
||||
continue
|
||||
}
|
||||
if sub.IsAvailableCommand() && sub.HasSubCommands() {
|
||||
if sub.IsAvailableCommand() && (isPlugin(sub) || sub.HasSubCommands()) {
|
||||
cmds = append(cmds, sub)
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,6 +56,33 @@ func TestInvalidPlugin(t *testing.T) {
|
||||
assert.DeepEqual(t, invalidPlugins(root), []*cobra.Command{sub1}, cmpopts.IgnoreUnexported(cobra.Command{}))
|
||||
}
|
||||
|
||||
func TestHiddenPlugin(t *testing.T) {
|
||||
root := &cobra.Command{Use: "root"}
|
||||
sub1 := &cobra.Command{
|
||||
Use: "sub1",
|
||||
Hidden: true,
|
||||
Annotations: map[string]string{
|
||||
metadata.CommandAnnotationPlugin: "true",
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
|
||||
sub1sub1 := &cobra.Command{Use: "sub1sub1"}
|
||||
sub1sub2 := &cobra.Command{Use: "sub1sub2"}
|
||||
sub2 := &cobra.Command{
|
||||
Use: "sub2",
|
||||
Annotations: map[string]string{
|
||||
metadata.CommandAnnotationPlugin: "true",
|
||||
},
|
||||
Run: func(cmd *cobra.Command, args []string) {},
|
||||
}
|
||||
|
||||
root.AddCommand(sub1, sub2)
|
||||
sub1.AddCommand(sub1sub1, sub1sub2)
|
||||
|
||||
assert.DeepEqual(t, allManagementSubCommands(root), []*cobra.Command{sub2}, cmpopts.IgnoreFields(cobra.Command{}, "Run"), cmpopts.IgnoreUnexported(cobra.Command{}))
|
||||
}
|
||||
|
||||
func TestCommandAliases(t *testing.T) {
|
||||
root := &cobra.Command{Use: "root"}
|
||||
sub := &cobra.Command{Use: "subcommand", Aliases: []string{"alias1", "alias2"}}
|
||||
|
||||
@ -85,9 +85,12 @@ func runPrune(ctx context.Context, dockerCli command.Cli, options pruneOptions)
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().BuildCachePrune(ctx, build.CachePruneOptions{
|
||||
All: options.all,
|
||||
KeepStorage: options.keepStorage.Value(), // FIXME(thaJeztah): rewrite to use new options; see https://github.com/moby/moby/pull/48720
|
||||
Filters: pruneFilters,
|
||||
All: options.all,
|
||||
// TODO(austinvazquez): remove when updated to use github.com/moby/moby/client@v0.1.0
|
||||
// See https://github.com/moby/moby/pull/50772 for more details.
|
||||
KeepStorage: options.keepStorage.Value(),
|
||||
ReservedSpace: options.keepStorage.Value(),
|
||||
Filters: pruneFilters,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
|
||||
@ -48,9 +48,7 @@ type Cli interface {
|
||||
Apply(ops ...CLIOption) error
|
||||
config.Provider
|
||||
ServerInfo() ServerInfo
|
||||
DefaultVersion() string
|
||||
CurrentVersion() string
|
||||
ContentTrustEnabled() bool
|
||||
BuildKitEnabled() (bool, error)
|
||||
ContextStore() store.Store
|
||||
CurrentContext() string
|
||||
@ -78,6 +76,7 @@ type DockerCli struct {
|
||||
dockerEndpoint docker.Endpoint
|
||||
contextStoreConfig *store.Config
|
||||
initTimeout time.Duration
|
||||
userAgent string
|
||||
res telemetryResource
|
||||
|
||||
// baseCtx is the base context used for internal operations. In the future
|
||||
@ -89,6 +88,8 @@ type DockerCli struct {
|
||||
}
|
||||
|
||||
// DefaultVersion returns [api.DefaultVersion].
|
||||
//
|
||||
// Deprecated: this function is no longer used and will be removed in the next release.
|
||||
func (*DockerCli) DefaultVersion() string {
|
||||
return api.DefaultVersion
|
||||
}
|
||||
@ -159,6 +160,8 @@ func (cli *DockerCli) ServerInfo() ServerInfo {
|
||||
|
||||
// ContentTrustEnabled returns whether content trust has been enabled by an
|
||||
// environment variable.
|
||||
//
|
||||
// Deprecated: check the value of the DOCKER_CONTENT_TRUST environment variable to detect whether content-trust is enabled.
|
||||
func (cli *DockerCli) ContentTrustEnabled() bool {
|
||||
return cli.contentTrust
|
||||
}
|
||||
@ -269,7 +272,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption)
|
||||
cli.contextStore = &ContextStoreWithDefault{
|
||||
Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig),
|
||||
Resolver: func() (*DefaultContext, error) {
|
||||
return ResolveDefaultContext(cli.options, *cli.contextStoreConfig)
|
||||
return resolveDefaultContext(cli.options, *cli.contextStoreConfig)
|
||||
},
|
||||
}
|
||||
|
||||
@ -306,17 +309,17 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.
|
||||
contextStore := &ContextStoreWithDefault{
|
||||
Store: store.New(config.ContextStoreDir(), storeConfig),
|
||||
Resolver: func() (*DefaultContext, error) {
|
||||
return ResolveDefaultContext(opts, storeConfig)
|
||||
return resolveDefaultContext(opts, storeConfig)
|
||||
},
|
||||
}
|
||||
endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to resolve docker endpoint")
|
||||
}
|
||||
return newAPIClientFromEndpoint(endpoint, configFile)
|
||||
return newAPIClientFromEndpoint(endpoint, configFile, client.WithUserAgent(UserAgent()))
|
||||
}
|
||||
|
||||
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile) (client.APIClient, error) {
|
||||
func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigFile, extraOpts ...client.Opt) (client.APIClient, error) {
|
||||
opts, err := ep.ClientOpts()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -324,7 +327,14 @@ func newAPIClientFromEndpoint(ep docker.Endpoint, configFile *configfile.ConfigF
|
||||
if len(configFile.HTTPHeaders) > 0 {
|
||||
opts = append(opts, client.WithHTTPHeaders(configFile.HTTPHeaders))
|
||||
}
|
||||
opts = append(opts, withCustomHeadersFromEnv(), client.WithUserAgent(UserAgent()))
|
||||
withCustomHeaders, err := withCustomHeadersFromEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if withCustomHeaders != nil {
|
||||
opts = append(opts, withCustomHeaders)
|
||||
}
|
||||
opts = append(opts, extraOpts...)
|
||||
return client.NewClientWithOpts(opts...)
|
||||
}
|
||||
|
||||
@ -545,7 +555,8 @@ func (cli *DockerCli) initialize() error {
|
||||
return
|
||||
}
|
||||
if cli.client == nil {
|
||||
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile); cli.initErr != nil {
|
||||
ops := []client.Opt{client.WithUserAgent(cli.userAgent)}
|
||||
if cli.client, cli.initErr = newAPIClientFromEndpoint(cli.dockerEndpoint, cli.configFile, ops...); cli.initErr != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -558,6 +569,8 @@ func (cli *DockerCli) initialize() error {
|
||||
}
|
||||
|
||||
// Apply all the operation on the cli
|
||||
//
|
||||
// Deprecated: this method is no longer used and will be removed in the next release if there are no remaining users.
|
||||
func (cli *DockerCli) Apply(ops ...CLIOption) error {
|
||||
for _, op := range ops {
|
||||
if err := op(cli); err != nil {
|
||||
@ -589,15 +602,18 @@ type ServerInfo struct {
|
||||
// environment.
|
||||
func NewDockerCli(ops ...CLIOption) (*DockerCli, error) {
|
||||
defaultOps := []CLIOption{
|
||||
WithContentTrustFromEnv(),
|
||||
withContentTrustFromEnv(),
|
||||
WithDefaultContextStoreConfig(),
|
||||
WithStandardStreams(),
|
||||
WithUserAgent(UserAgent()),
|
||||
}
|
||||
ops = append(defaultOps, ops...)
|
||||
|
||||
cli := &DockerCli{baseCtx: context.Background()}
|
||||
if err := cli.Apply(ops...); err != nil {
|
||||
return nil, err
|
||||
for _, op := range ops {
|
||||
if err := op(cli); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cli, nil
|
||||
}
|
||||
@ -613,7 +629,7 @@ func getServerHost(hosts []string, defaultToTLS bool) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// UserAgent returns the user agent string used for making API requests
|
||||
// UserAgent returns the default user agent string used for making API requests.
|
||||
func UserAgent() string {
|
||||
return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")"
|
||||
}
|
||||
|
||||
@ -75,8 +75,8 @@ func WithErrorStream(err io.Writer) CLIOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
|
||||
func WithContentTrustFromEnv() CLIOption {
|
||||
// withContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
|
||||
func withContentTrustFromEnv() CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
cli.contentTrust = false
|
||||
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
|
||||
@ -89,7 +89,16 @@ func WithContentTrustFromEnv() CLIOption {
|
||||
}
|
||||
}
|
||||
|
||||
// WithContentTrustFromEnv enables content trust on a cli from environment variable DOCKER_CONTENT_TRUST value.
|
||||
//
|
||||
// Deprecated: this option is no longer used, and will be removed in the next release.
|
||||
func WithContentTrustFromEnv() CLIOption {
|
||||
return withContentTrustFromEnv()
|
||||
}
|
||||
|
||||
// WithContentTrust enables content trust on a cli.
|
||||
//
|
||||
// Deprecated: this option is no longer used, and will be removed in the next release.
|
||||
func WithContentTrust(enabled bool) CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
cli.contentTrust = enabled
|
||||
@ -180,61 +189,70 @@ const envOverrideHTTPHeaders = "DOCKER_CUSTOM_HEADERS"
|
||||
// override headers with the same name).
|
||||
//
|
||||
// TODO(thaJeztah): this is a client Option, and should be moved to the client. It is non-exported for that reason.
|
||||
func withCustomHeadersFromEnv() client.Opt {
|
||||
return func(apiClient *client.Client) error {
|
||||
value := os.Getenv(envOverrideHTTPHeaders)
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
csvReader := csv.NewReader(strings.NewReader(value))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return invalidParameter(errors.Errorf(
|
||||
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
|
||||
envOverrideHTTPHeaders,
|
||||
func withCustomHeadersFromEnv() (client.Opt, error) {
|
||||
value := os.Getenv(envOverrideHTTPHeaders)
|
||||
if value == "" {
|
||||
return nil, nil
|
||||
}
|
||||
csvReader := csv.NewReader(strings.NewReader(value))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return nil, invalidParameter(errors.Errorf(
|
||||
"failed to parse custom headers from %s environment variable: value must be formatted as comma-separated key=value pairs",
|
||||
envOverrideHTTPHeaders,
|
||||
))
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
env := map[string]string{}
|
||||
for _, kv := range fields {
|
||||
k, v, hasValue := strings.Cut(kv, "=")
|
||||
|
||||
// Only strip whitespace in keys; preserve whitespace in values.
|
||||
k = strings.TrimSpace(k)
|
||||
|
||||
if k == "" {
|
||||
return nil, invalidParameter(errors.Errorf(
|
||||
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
|
||||
envOverrideHTTPHeaders, kv,
|
||||
))
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
return nil
|
||||
|
||||
// We don't currently allow empty key=value pairs, and produce an error.
|
||||
// This is something we could allow in future (e.g. to read value
|
||||
// from an environment variable with the same name). In the meantime,
|
||||
// produce an error to prevent users from depending on this.
|
||||
if !hasValue {
|
||||
return nil, invalidParameter(errors.Errorf(
|
||||
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
|
||||
envOverrideHTTPHeaders, kv,
|
||||
))
|
||||
}
|
||||
|
||||
env := map[string]string{}
|
||||
for _, kv := range fields {
|
||||
k, v, hasValue := strings.Cut(kv, "=")
|
||||
env[http.CanonicalHeaderKey(k)] = v
|
||||
}
|
||||
|
||||
// Only strip whitespace in keys; preserve whitespace in values.
|
||||
k = strings.TrimSpace(k)
|
||||
if len(env) == 0 {
|
||||
// We should probably not hit this case, as we don't skip values
|
||||
// (only return errors), but we don't want to discard existing
|
||||
// headers with an empty set.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if k == "" {
|
||||
return invalidParameter(errors.Errorf(
|
||||
`failed to set custom headers from %s environment variable: value contains a key=value pair with an empty key: '%s'`,
|
||||
envOverrideHTTPHeaders, kv,
|
||||
))
|
||||
}
|
||||
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
|
||||
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
|
||||
return client.WithHTTPHeaders(env), nil
|
||||
}
|
||||
|
||||
// We don't currently allow empty key=value pairs, and produce an error.
|
||||
// This is something we could allow in future (e.g. to read value
|
||||
// from an environment variable with the same name). In the meantime,
|
||||
// produce an error to prevent users from depending on this.
|
||||
if !hasValue {
|
||||
return invalidParameter(errors.Errorf(
|
||||
`failed to set custom headers from %s environment variable: missing "=" in key=value pair: '%s'`,
|
||||
envOverrideHTTPHeaders, kv,
|
||||
))
|
||||
}
|
||||
|
||||
env[http.CanonicalHeaderKey(k)] = v
|
||||
// WithUserAgent configures the User-Agent string for cli HTTP requests.
|
||||
func WithUserAgent(userAgent string) CLIOption {
|
||||
return func(cli *DockerCli) error {
|
||||
if userAgent == "" {
|
||||
return errors.New("user agent cannot be blank")
|
||||
}
|
||||
|
||||
if len(env) == 0 {
|
||||
// We should probably not hit this case, as we don't skip values
|
||||
// (only return errors), but we don't want to discard existing
|
||||
// headers with an empty set.
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(thaJeztah): add a client.WithExtraHTTPHeaders() function to allow these headers to be _added_ to existing ones, instead of _replacing_
|
||||
// see https://github.com/docker/cli/pull/5098#issuecomment-2147403871 (when updating, also update the WARNING in the function and env-var GoDoc)
|
||||
return client.WithHTTPHeaders(env)(apiClient)
|
||||
cli.userAgent = userAgent
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
func contentTrustEnabled(t *testing.T) bool {
|
||||
t.Helper()
|
||||
var cli DockerCli
|
||||
assert.NilError(t, WithContentTrustFromEnv()(&cli))
|
||||
assert.NilError(t, withContentTrustFromEnv()(&cli))
|
||||
return cli.contentTrust
|
||||
}
|
||||
|
||||
|
||||
@ -374,3 +374,26 @@ func TestSetGoDebug(t *testing.T) {
|
||||
assert.Equal(t, "val1,val2=1", os.Getenv("GODEBUG"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewDockerCliWithCustomUserAgent(t *testing.T) {
|
||||
var received string
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
received = r.UserAgent()
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer ts.Close()
|
||||
host := strings.Replace(ts.URL, "http://", "tcp://", 1)
|
||||
opts := &flags.ClientOptions{Hosts: []string{host}}
|
||||
|
||||
cli, err := NewDockerCli(
|
||||
WithUserAgent("fake-agent/0.0.1"),
|
||||
)
|
||||
assert.NilError(t, err)
|
||||
cli.currentContext = DefaultContextName
|
||||
cli.options = opts
|
||||
cli.configFile = &configfile.ConfigFile{}
|
||||
|
||||
_, err = cli.Client().Ping(context.Background())
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, received, "fake-agent/0.0.1")
|
||||
}
|
||||
|
||||
@ -96,7 +96,7 @@ func newCreateCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
addPlatformFlag(flags, &options.platform)
|
||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
||||
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification")
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||
copts = addFlags(flags)
|
||||
|
||||
addCompletions(cmd, dockerCLI)
|
||||
|
||||
@ -249,6 +249,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("DOCKER_CONTENT_TRUST", "true")
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
@ -258,7 +259,7 @@ func TestNewCreateCommandWithContentTrustErrors(t *testing.T) {
|
||||
) (container.CreateResponse, error) {
|
||||
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
})
|
||||
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := newCreateCommand(fakeCLI)
|
||||
cmd.SetOut(io.Discard)
|
||||
|
||||
@ -167,6 +167,7 @@ func (c *statsContext) Container() string {
|
||||
}
|
||||
|
||||
func (c *statsContext) Name() string {
|
||||
// TODO(thaJeztah): make this explicitly trim the "/" prefix, not just any char.
|
||||
if len(c.s.Name) > 1 {
|
||||
return c.s.Name[1:]
|
||||
}
|
||||
|
||||
@ -5,45 +5,181 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"gotest.tools/v3/assert"
|
||||
is "gotest.tools/v3/assert/cmp"
|
||||
)
|
||||
|
||||
func TestContainerStatsContext(t *testing.T) {
|
||||
containerID := test.RandomID()
|
||||
const actorID = "c74518277ddc15a6afeaaeb06ee5f7433dcb27188224777c1efa7df1e8766d65"
|
||||
|
||||
var ctx statsContext
|
||||
tt := []struct {
|
||||
tests := []struct {
|
||||
name string
|
||||
stats StatsEntry
|
||||
osType string
|
||||
expValue string
|
||||
expHeader string
|
||||
call func() string
|
||||
}{
|
||||
{StatsEntry{Container: containerID}, "", containerID, containerHeader, ctx.Container},
|
||||
{StatsEntry{CPUPercentage: 5.5}, "", "5.50%", cpuPercHeader, ctx.CPUPerc},
|
||||
{StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, "", "--", cpuPercHeader, ctx.CPUPerc},
|
||||
{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, "", "0.31B / 12.3B", netIOHeader, ctx.NetIO},
|
||||
{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "", "--", netIOHeader, ctx.NetIO},
|
||||
{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "", "0.1B / 2.3B", blockIOHeader, ctx.BlockIO},
|
||||
{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true}, "", "--", blockIOHeader, ctx.BlockIO},
|
||||
{StatsEntry{MemoryPercentage: 10.2}, "", "10.20%", memPercHeader, ctx.MemPerc},
|
||||
{StatsEntry{MemoryPercentage: 10.2, IsInvalid: true}, "", "--", memPercHeader, ctx.MemPerc},
|
||||
{StatsEntry{MemoryPercentage: 10.2}, "windows", "--", memPercHeader, ctx.MemPerc},
|
||||
{StatsEntry{Memory: 24, MemoryLimit: 30}, "", "24B / 30B", memUseHeader, ctx.MemUsage},
|
||||
{StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "", "-- / --", memUseHeader, ctx.MemUsage},
|
||||
{StatsEntry{Memory: 24, MemoryLimit: 30}, "windows", "24B", winMemUseHeader, ctx.MemUsage},
|
||||
{StatsEntry{PidsCurrent: 10}, "", "10", pidsHeader, ctx.PIDs},
|
||||
{StatsEntry{PidsCurrent: 10, IsInvalid: true}, "", "--", pidsHeader, ctx.PIDs},
|
||||
{StatsEntry{PidsCurrent: 10}, "windows", "--", pidsHeader, ctx.PIDs},
|
||||
{
|
||||
name: "Container id",
|
||||
stats: StatsEntry{ID: actorID, Container: actorID},
|
||||
expValue: actorID,
|
||||
expHeader: containerHeader,
|
||||
call: ctx.Container,
|
||||
},
|
||||
{
|
||||
name: "Container name",
|
||||
stats: StatsEntry{ID: actorID, Container: "a-long-container-name"},
|
||||
expValue: "a-long-container-name",
|
||||
expHeader: containerHeader,
|
||||
call: ctx.Container,
|
||||
},
|
||||
{
|
||||
name: "ID",
|
||||
stats: StatsEntry{ID: actorID},
|
||||
expValue: actorID,
|
||||
expHeader: formatter.ContainerIDHeader,
|
||||
call: ctx.ID,
|
||||
},
|
||||
{
|
||||
name: "Name",
|
||||
stats: StatsEntry{Name: "/container-name"},
|
||||
expValue: "container-name",
|
||||
expHeader: formatter.ContainerIDHeader,
|
||||
call: ctx.Name,
|
||||
},
|
||||
{
|
||||
name: "Name empty",
|
||||
stats: StatsEntry{Name: ""},
|
||||
expValue: "--",
|
||||
expHeader: formatter.ContainerIDHeader,
|
||||
call: ctx.Name,
|
||||
},
|
||||
{
|
||||
name: "Name prefix only",
|
||||
stats: StatsEntry{Name: "/"},
|
||||
expValue: "--",
|
||||
expHeader: formatter.ContainerIDHeader,
|
||||
call: ctx.Name,
|
||||
},
|
||||
{
|
||||
name: "CPUPerc",
|
||||
stats: StatsEntry{CPUPercentage: 5.5},
|
||||
expValue: "5.50%",
|
||||
expHeader: cpuPercHeader,
|
||||
call: ctx.CPUPerc,
|
||||
},
|
||||
{
|
||||
name: "CPUPerc invalid",
|
||||
stats: StatsEntry{CPUPercentage: 5.5, IsInvalid: true},
|
||||
expValue: "--",
|
||||
expHeader: cpuPercHeader,
|
||||
call: ctx.CPUPerc,
|
||||
},
|
||||
{
|
||||
name: "NetIO",
|
||||
stats: StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3},
|
||||
expValue: "0.31B / 12.3B",
|
||||
expHeader: netIOHeader,
|
||||
call: ctx.NetIO,
|
||||
},
|
||||
{
|
||||
name: "NetIO invalid",
|
||||
stats: StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true},
|
||||
expValue: "--",
|
||||
expHeader: netIOHeader,
|
||||
call: ctx.NetIO,
|
||||
},
|
||||
{
|
||||
name: "BlockIO",
|
||||
stats: StatsEntry{BlockRead: 0.1, BlockWrite: 2.3},
|
||||
expValue: "0.1B / 2.3B",
|
||||
expHeader: blockIOHeader,
|
||||
call: ctx.BlockIO,
|
||||
},
|
||||
{
|
||||
name: "BlockIO invalid",
|
||||
stats: StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true},
|
||||
expValue: "--",
|
||||
expHeader: blockIOHeader,
|
||||
call: ctx.BlockIO,
|
||||
},
|
||||
{
|
||||
name: "MemPerc",
|
||||
stats: StatsEntry{MemoryPercentage: 10.2},
|
||||
expValue: "10.20%",
|
||||
expHeader: memPercHeader,
|
||||
call: ctx.MemPerc,
|
||||
},
|
||||
{
|
||||
name: "MemPerc invalid",
|
||||
stats: StatsEntry{MemoryPercentage: 10.2, IsInvalid: true},
|
||||
expValue: "--",
|
||||
expHeader: memPercHeader,
|
||||
call: ctx.MemPerc,
|
||||
},
|
||||
{
|
||||
name: "MemPerc windows",
|
||||
stats: StatsEntry{MemoryPercentage: 10.2},
|
||||
osType: "windows",
|
||||
expValue: "--",
|
||||
expHeader: memPercHeader,
|
||||
call: ctx.MemPerc,
|
||||
},
|
||||
{
|
||||
name: "MemUsage",
|
||||
stats: StatsEntry{Memory: 24, MemoryLimit: 30},
|
||||
expValue: "24B / 30B",
|
||||
expHeader: memUseHeader,
|
||||
call: ctx.MemUsage,
|
||||
},
|
||||
{
|
||||
name: "MemUsage invalid",
|
||||
stats: StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true},
|
||||
expValue: "-- / --",
|
||||
expHeader: memUseHeader,
|
||||
call: ctx.MemUsage,
|
||||
},
|
||||
{
|
||||
name: "MemUsage windows",
|
||||
stats: StatsEntry{Memory: 24, MemoryLimit: 30},
|
||||
osType: "windows",
|
||||
expValue: "24B",
|
||||
expHeader: winMemUseHeader,
|
||||
call: ctx.MemUsage,
|
||||
},
|
||||
{
|
||||
name: "PIDs",
|
||||
stats: StatsEntry{PidsCurrent: 10},
|
||||
expValue: "10",
|
||||
expHeader: pidsHeader,
|
||||
call: ctx.PIDs,
|
||||
},
|
||||
{
|
||||
name: "PIDs invalid",
|
||||
stats: StatsEntry{PidsCurrent: 10, IsInvalid: true},
|
||||
expValue: "--",
|
||||
expHeader: pidsHeader,
|
||||
call: ctx.PIDs,
|
||||
},
|
||||
{
|
||||
name: "PIDs windows",
|
||||
stats: StatsEntry{PidsCurrent: 10},
|
||||
osType: "windows",
|
||||
expValue: "--",
|
||||
expHeader: pidsHeader,
|
||||
call: ctx.PIDs,
|
||||
},
|
||||
}
|
||||
|
||||
for _, te := range tt {
|
||||
ctx = statsContext{s: te.stats, os: te.osType}
|
||||
if v := te.call(); v != te.expValue {
|
||||
t.Fatalf("Expected %q, got %q", te.expValue, v)
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx = statsContext{s: tc.stats, os: tc.osType}
|
||||
if v := tc.call(); v != tc.expValue {
|
||||
t.Fatalf("Expected %q, got %q", tc.expValue, v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -87,7 +87,7 @@ func buildContainerListOptions(options *psOptions) (*container.ListOptions, erro
|
||||
|
||||
// always validate template when `--format` is used, for consistency
|
||||
if len(options.format) > 0 {
|
||||
tmpl, err := templates.NewParse("", options.format)
|
||||
tmpl, err := templates.Parse(options.format)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse template")
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@ -350,7 +351,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions, serverOS string) (*con
|
||||
|
||||
// Validate the input mac address
|
||||
if copts.macAddress != "" {
|
||||
if _, err := opts.ValidateMACAddress(copts.macAddress); err != nil {
|
||||
if _, err := net.ParseMAC(strings.TrimSpace(copts.macAddress)); err != nil {
|
||||
return nil, errors.Errorf("%s is not a valid mac address", copts.macAddress)
|
||||
}
|
||||
}
|
||||
@ -883,7 +884,7 @@ func parseNetworkAttachmentOpt(ep opts.NetworkAttachmentOpts) (*networktypes.End
|
||||
}
|
||||
}
|
||||
if ep.MacAddress != "" {
|
||||
if _, err := opts.ValidateMACAddress(ep.MacAddress); err != nil {
|
||||
if _, err := net.ParseMAC(strings.TrimSpace(ep.MacAddress)); err != nil {
|
||||
return nil, errors.Errorf("%s is not a valid mac address", ep.MacAddress)
|
||||
}
|
||||
epConfig.MacAddress = ep.MacAddress
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/moby/sys/signal"
|
||||
@ -74,7 +75,7 @@ func newRunCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
|
||||
// TODO(thaJeztah): consider adding platform as "image create option" on containerOptions
|
||||
addPlatformFlag(flags, &options.platform)
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification")
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||
copts = addFlags(flags)
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys)
|
||||
|
||||
@ -323,6 +323,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("DOCKER_CONTENT_TRUST", "true")
|
||||
fakeCLI := test.NewFakeCli(&fakeClient{
|
||||
createContainerFunc: func(config *container.Config,
|
||||
hostConfig *container.HostConfig,
|
||||
@ -332,7 +333,7 @@ func TestRunCommandWithContentTrustErrors(t *testing.T) {
|
||||
) (container.CreateResponse, error) {
|
||||
return container.CreateResponse{}, errors.New("shouldn't try to pull image")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
})
|
||||
fakeCLI.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := newRunCommand(fakeCLI)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
@ -139,7 +139,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
eh := newEventHandler()
|
||||
if options.All {
|
||||
eh.setHandler(events.ActionCreate, func(e events.Message) {
|
||||
s := NewStats(e.Actor.ID[:12])
|
||||
s := NewStats(e.Actor.ID)
|
||||
if cStats.add(s) {
|
||||
waitFirst.Add(1)
|
||||
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
||||
@ -148,7 +148,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
}
|
||||
|
||||
eh.setHandler(events.ActionStart, func(e events.Message) {
|
||||
s := NewStats(e.Actor.ID[:12])
|
||||
s := NewStats(e.Actor.ID)
|
||||
if cStats.add(s) {
|
||||
waitFirst.Add(1)
|
||||
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
||||
@ -157,7 +157,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
|
||||
if !options.All {
|
||||
eh.setHandler(events.ActionDie, func(e events.Message) {
|
||||
cStats.remove(e.Actor.ID[:12])
|
||||
cStats.remove(e.Actor.ID)
|
||||
})
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ func RunStats(ctx context.Context, dockerCLI command.Cli, options *StatsOptions)
|
||||
return err
|
||||
}
|
||||
for _, ctr := range cs {
|
||||
s := NewStats(ctr.ID[:12])
|
||||
s := NewStats(ctr.ID)
|
||||
if cStats.add(s) {
|
||||
waitFirst.Add(1)
|
||||
go collect(ctx, s, apiClient, !options.NoStream, waitFirst)
|
||||
@ -363,7 +363,12 @@ func (eh *eventHandler) watch(c <-chan events.Message) {
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
logrus.Debugf("event handler: received event: %v", e)
|
||||
if e.Actor.ID == "" {
|
||||
logrus.WithField("event", e).Errorf("event handler: received %s event with empty ID", e.Action)
|
||||
continue
|
||||
}
|
||||
|
||||
logrus.WithField("event", e).Debugf("event handler: received %s event for: %s", e.Action, e.Actor.ID)
|
||||
go h(e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +52,14 @@ type EndpointDefaultResolver interface {
|
||||
}
|
||||
|
||||
// ResolveDefaultContext creates a Metadata for the current CLI invocation parameters
|
||||
//
|
||||
// Deprecated: this function is exported for testing and meant for internal use. It will be removed in the next release.
|
||||
func ResolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) {
|
||||
return resolveDefaultContext(opts, config)
|
||||
}
|
||||
|
||||
// resolveDefaultContext creates a Metadata for the current CLI invocation parameters
|
||||
func resolveDefaultContext(opts *cliflags.ClientOptions, config store.Config) (*DefaultContext, error) {
|
||||
contextTLSData := store.ContextTLSData{
|
||||
Endpoints: make(map[string]store.EndpointTLSData),
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ func TestDefaultContextInitializer(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
t.Setenv("DOCKER_HOST", "ssh://someswarmserver")
|
||||
cli.configFile = &configfile.ConfigFile{}
|
||||
ctx, err := ResolveDefaultContext(&cliflags.ClientOptions{
|
||||
ctx, err := resolveDefaultContext(&cliflags.ClientOptions{
|
||||
TLS: true,
|
||||
TLSOptions: &tlsconfig.Options{
|
||||
CAFile: "./testdata/ca.pem",
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
// based on https://github.com/golang/go/blob/master/src/text/tabwriter/tabwriter.go Last modified 690ac40 on 31 Jan
|
||||
|
||||
//nolint:gocyclo,nakedret,unused // ignore linting errors, so that we can stick close to upstream
|
||||
//nolint:gocyclo,gofumpt,nakedret,unused // ignore linting errors, so that we can stick close to upstream
|
||||
package tabwriter
|
||||
|
||||
import (
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
@ -152,7 +151,7 @@ func newBuildCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/buildx/build/#target"})
|
||||
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
||||
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification")
|
||||
flags.BoolVar(&options.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||
|
||||
flags.StringVar(&options.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Set platform if server is multi-platform capable")
|
||||
flags.SetAnnotation("platform", "version", []string{"1.38"})
|
||||
@ -185,7 +184,6 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
|
||||
//nolint:gocyclo
|
||||
func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) error {
|
||||
var (
|
||||
err error
|
||||
buildCtx io.ReadCloser
|
||||
dockerfileCtx io.ReadCloser
|
||||
contextDir string
|
||||
@ -267,7 +265,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
||||
}
|
||||
|
||||
if err := build.ValidateContextDirectory(contextDir, excludes); err != nil {
|
||||
return errors.Wrap(err, "error checking context")
|
||||
return errors.Wrap(err, "checking context")
|
||||
}
|
||||
|
||||
// And canonicalize dockerfile name to a platform-independent one
|
||||
@ -341,11 +339,19 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
||||
configFile := dockerCli.ConfigFile()
|
||||
creds, _ := configFile.GetAllCredentials()
|
||||
authConfigs := make(map[string]registrytypes.AuthConfig, len(creds))
|
||||
for k, auth := range creds {
|
||||
authConfigs[k] = registrytypes.AuthConfig(auth)
|
||||
for k, authConfig := range creds {
|
||||
authConfigs[k] = registrytypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
}
|
||||
}
|
||||
buildOpts := imageBuildOptions(dockerCli, options)
|
||||
buildOpts.Version = buildtypes.BuilderV1
|
||||
buildOpts.Dockerfile = relDockerfile
|
||||
buildOpts.AuthConfigs = authConfigs
|
||||
buildOpts.RemoteContext = remote
|
||||
@ -355,7 +361,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
||||
if options.quiet {
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "%s", progBuff)
|
||||
}
|
||||
cancel()
|
||||
return err
|
||||
}
|
||||
defer response.Body.Close()
|
||||
@ -372,7 +377,8 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
||||
|
||||
err = jsonstream.Display(ctx, response.Body, streams.NewOut(buildBuff), jsonstream.WithAuxCallback(aux))
|
||||
if err != nil {
|
||||
if jerr, ok := err.(*jsonstream.JSONError); ok {
|
||||
var jerr *jsonstream.JSONError
|
||||
if errors.As(err, &jerr) {
|
||||
// If no error code is set, default to 1
|
||||
if jerr.Code == 0 {
|
||||
jerr.Code = 1
|
||||
@ -385,16 +391,6 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
|
||||
return err
|
||||
}
|
||||
|
||||
// Windows: show error message about modified file permissions if the
|
||||
// daemon isn't running Windows.
|
||||
if response.OSType != "windows" && runtime.GOOS == "windows" && !options.quiet {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), "SECURITY WARNING: You are building a Docker "+
|
||||
"image from Windows against a non-Windows Docker host. All files and "+
|
||||
"directories added to build context will have '-rwxr-xr-x' permissions. "+
|
||||
"It is recommended to double check and reset permissions for sensitive "+
|
||||
"files and directories.")
|
||||
}
|
||||
|
||||
// Everything worked so if -q was provided the output from the daemon
|
||||
// should be just the image ID and we'll print that to stdout.
|
||||
if options.quiet {
|
||||
@ -546,6 +542,7 @@ func replaceDockerfileForContentTrust(ctx context.Context, inputTarStream io.Rea
|
||||
func imageBuildOptions(dockerCli command.Cli, options buildOptions) buildtypes.ImageBuildOptions {
|
||||
configFile := dockerCli.ConfigFile()
|
||||
return buildtypes.ImageBuildOptions{
|
||||
Version: buildtypes.BuilderV1,
|
||||
Memory: options.memory.Value(),
|
||||
MemorySwap: options.memorySwap.Value(),
|
||||
Tags: options.tags.GetSlice(),
|
||||
|
||||
@ -25,9 +25,14 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
|
||||
//
|
||||
// Deprecated: this const is no longer used and will be removed in the next release.
|
||||
const DefaultDockerfileName string = "Dockerfile"
|
||||
|
||||
const (
|
||||
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
|
||||
DefaultDockerfileName string = "Dockerfile"
|
||||
// defaultDockerfileName is the Default filename with Docker commands, read by docker build
|
||||
defaultDockerfileName string = "Dockerfile"
|
||||
// archiveHeaderSize is the number of bytes in an archive header
|
||||
archiveHeaderSize = 512
|
||||
)
|
||||
@ -80,7 +85,7 @@ func ValidateContextDirectory(srcPath string, excludes []string) error {
|
||||
if err != nil && os.IsPermission(err) {
|
||||
return errors.Errorf("no permission to read from '%s'", filePath)
|
||||
}
|
||||
currentFile.Close()
|
||||
_ = currentFile.Close()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@ -97,10 +102,21 @@ func filepathMatches(matcher *patternmatcher.PatternMatcher, file string) (bool,
|
||||
|
||||
// DetectArchiveReader detects whether the input stream is an archive or a
|
||||
// Dockerfile and returns a buffered version of input, safe to consume in lieu
|
||||
// of input. If an archive is detected, isArchive is set to true, and to false
|
||||
// of input. If an archive is detected, ok is set to true, and to false
|
||||
// otherwise, in which case it is safe to assume input represents the contents
|
||||
// of a Dockerfile.
|
||||
func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, isArchive bool, err error) {
|
||||
//
|
||||
// Deprecated: this utility was only used internally, and will be removed in the next release.
|
||||
func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err error) {
|
||||
return detectArchiveReader(input)
|
||||
}
|
||||
|
||||
// detectArchiveReader detects whether the input stream is an archive or a
|
||||
// Dockerfile and returns a buffered version of input, safe to consume in lieu
|
||||
// of input. If an archive is detected, ok is set to true, and to false
|
||||
// otherwise, in which case it is safe to assume input represents the contents
|
||||
// of a Dockerfile.
|
||||
func detectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, ok bool, err error) {
|
||||
buf := bufio.NewReader(input)
|
||||
|
||||
magic, err := buf.Peek(archiveHeaderSize * 2)
|
||||
@ -108,13 +124,22 @@ func DetectArchiveReader(input io.ReadCloser) (rc io.ReadCloser, isArchive bool,
|
||||
return nil, false, errors.Errorf("failed to peek context header from STDIN: %v", err)
|
||||
}
|
||||
|
||||
return newReadCloserWrapper(buf, func() error { return input.Close() }), IsArchive(magic), nil
|
||||
return newReadCloserWrapper(buf, func() error { return input.Close() }), isArchive(magic), nil
|
||||
}
|
||||
|
||||
// WriteTempDockerfile writes a Dockerfile stream to a temporary file with a
|
||||
// name specified by DefaultDockerfileName and returns the path to the
|
||||
// name specified by defaultDockerfileName and returns the path to the
|
||||
// temporary directory containing the Dockerfile.
|
||||
//
|
||||
// Deprecated: this utility was only used internally, and will be removed in the next release.
|
||||
func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
|
||||
return writeTempDockerfile(rc)
|
||||
}
|
||||
|
||||
// writeTempDockerfile writes a Dockerfile stream to a temporary file with a
|
||||
// name specified by defaultDockerfileName and returns the path to the
|
||||
// temporary directory containing the Dockerfile.
|
||||
func writeTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
|
||||
// err is a named return value, due to the defer call below.
|
||||
dockerfileDir, err = os.MkdirTemp("", "docker-build-tempdockerfile-")
|
||||
if err != nil {
|
||||
@ -126,7 +151,7 @@ func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
f, err := os.Create(filepath.Join(dockerfileDir, DefaultDockerfileName))
|
||||
f, err := os.Create(filepath.Join(dockerfileDir, defaultDockerfileName))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -141,12 +166,12 @@ func WriteTempDockerfile(rc io.ReadCloser) (dockerfileDir string, err error) {
|
||||
// Dockerfile or tar archive. Returns a tar archive used as a context and a
|
||||
// path to the Dockerfile inside the tar.
|
||||
func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
|
||||
rc, isArchive, err := DetectArchiveReader(rc)
|
||||
rc, ok, err := detectArchiveReader(rc)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if isArchive {
|
||||
if ok {
|
||||
return rc, dockerfileName, nil
|
||||
}
|
||||
|
||||
@ -159,7 +184,7 @@ func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadC
|
||||
return nil, "", errors.New("ambiguous Dockerfile source: both stdin and flag correspond to Dockerfiles")
|
||||
}
|
||||
|
||||
dockerfileDir, err := WriteTempDockerfile(rc)
|
||||
dockerfileDir, err := writeTempDockerfile(rc)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -171,14 +196,22 @@ func GetContextFromReader(rc io.ReadCloser, dockerfileName string) (out io.ReadC
|
||||
|
||||
return newReadCloserWrapper(tarArchive, func() error {
|
||||
err := tarArchive.Close()
|
||||
os.RemoveAll(dockerfileDir)
|
||||
_ = os.RemoveAll(dockerfileDir)
|
||||
return err
|
||||
}), DefaultDockerfileName, nil
|
||||
}), defaultDockerfileName, nil
|
||||
}
|
||||
|
||||
// IsArchive checks for the magic bytes of a tar or any supported compression
|
||||
// algorithm.
|
||||
//
|
||||
// Deprecated: this utility was used internally and will be removed in the next release.
|
||||
func IsArchive(header []byte) bool {
|
||||
return isArchive(header)
|
||||
}
|
||||
|
||||
// isArchive checks for the magic bytes of a tar or any supported compression
|
||||
// algorithm.
|
||||
func isArchive(header []byte) bool {
|
||||
if compression.Detect(header) != compression.None {
|
||||
return true
|
||||
}
|
||||
@ -201,7 +234,7 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error)
|
||||
return "", "", errors.Wrapf(err, "unable to 'git clone' to temporary context directory")
|
||||
}
|
||||
|
||||
absContextDir, err = ResolveAndValidateContextPath(absContextDir)
|
||||
absContextDir, err = resolveAndValidateContextPath(absContextDir)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@ -242,7 +275,7 @@ func getWithStatusError(url string) (resp *http.Response, err error) {
|
||||
}
|
||||
msg := fmt.Sprintf("failed to GET %s with status %s", url, resp.Status)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
_ = resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "%s: error reading body", msg)
|
||||
}
|
||||
@ -254,7 +287,7 @@ func getWithStatusError(url string) (resp *http.Response, err error) {
|
||||
// the relative path of the dockerfile in that context directory, and a non-nil
|
||||
// error on success.
|
||||
func GetContextFromLocalDir(localDir, dockerfileName string) (string, string, error) {
|
||||
localDir, err := ResolveAndValidateContextPath(localDir)
|
||||
localDir, err := resolveAndValidateContextPath(localDir)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
@ -274,7 +307,18 @@ func GetContextFromLocalDir(localDir, dockerfileName string) (string, string, er
|
||||
|
||||
// ResolveAndValidateContextPath uses the given context directory for a `docker build`
|
||||
// and returns the absolute path to the context directory.
|
||||
//
|
||||
// Deprecated: this utility was used internally and will be removed in the next
|
||||
// release. Use [DetectContextType] to detect the context-type, and use
|
||||
// [GetContextFromLocalDir], [GetContextFromLocalDir], [GetContextFromGitURL],
|
||||
// or [GetContextFromURL] instead.
|
||||
func ResolveAndValidateContextPath(givenContextDir string) (string, error) {
|
||||
return resolveAndValidateContextPath(givenContextDir)
|
||||
}
|
||||
|
||||
// resolveAndValidateContextPath uses the given context directory for a `docker build`
|
||||
// and returns the absolute path to the context directory.
|
||||
func resolveAndValidateContextPath(givenContextDir string) (string, error) {
|
||||
absContextDir, err := filepath.Abs(givenContextDir)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("unable to get absolute context directory of given context directory %q: %v", givenContextDir, err)
|
||||
@ -318,12 +362,12 @@ func getDockerfileRelPath(absContextDir, givenDockerfile string) (string, error)
|
||||
if absDockerfile == "" {
|
||||
// No -f/--file was specified so use the default relative to the
|
||||
// context directory.
|
||||
absDockerfile = filepath.Join(absContextDir, DefaultDockerfileName)
|
||||
absDockerfile = filepath.Join(absContextDir, defaultDockerfileName)
|
||||
|
||||
// Just to be nice ;-) look for 'dockerfile' too but only
|
||||
// use it if we found it, otherwise ignore this check
|
||||
if _, err = os.Lstat(absDockerfile); os.IsNotExist(err) {
|
||||
altPath := filepath.Join(absContextDir, strings.ToLower(DefaultDockerfileName))
|
||||
altPath := filepath.Join(absContextDir, strings.ToLower(defaultDockerfileName))
|
||||
if _, err = os.Lstat(altPath); err == nil {
|
||||
absDockerfile = altPath
|
||||
}
|
||||
@ -374,7 +418,7 @@ func isUNC(path string) bool {
|
||||
// the relative path to the dockerfile in the context.
|
||||
func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCloser) (io.ReadCloser, string, error) {
|
||||
file, err := io.ReadAll(dockerfileCtx)
|
||||
dockerfileCtx.Close()
|
||||
_ = dockerfileCtx.Close()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@ -438,17 +482,19 @@ func Compress(buildCtx io.ReadCloser) (io.ReadCloser, error) {
|
||||
go func() {
|
||||
compressWriter, err := compression.CompressStream(pipeWriter, archive.Gzip)
|
||||
if err != nil {
|
||||
pipeWriter.CloseWithError(err)
|
||||
_ = pipeWriter.CloseWithError(err)
|
||||
}
|
||||
defer buildCtx.Close()
|
||||
defer func() {
|
||||
_ = buildCtx.Close()
|
||||
}()
|
||||
|
||||
if _, err := io.Copy(compressWriter, buildCtx); err != nil {
|
||||
pipeWriter.CloseWithError(errors.Wrap(err, "failed to compress context"))
|
||||
compressWriter.Close()
|
||||
_ = pipeWriter.CloseWithError(errors.Wrap(err, "failed to compress context"))
|
||||
_ = compressWriter.Close()
|
||||
return
|
||||
}
|
||||
compressWriter.Close()
|
||||
pipeWriter.Close()
|
||||
_ = compressWriter.Close()
|
||||
_ = pipeWriter.Close()
|
||||
}()
|
||||
|
||||
return pipeReader, nil
|
||||
|
||||
@ -31,7 +31,7 @@ func prepareNoFiles(t *testing.T) string {
|
||||
func prepareOneFile(t *testing.T) string {
|
||||
t.Helper()
|
||||
contextDir := createTestTempDir(t)
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
return contextDir
|
||||
}
|
||||
|
||||
@ -66,7 +66,7 @@ func TestGetContextFromLocalDirNotExistingDockerfile(t *testing.T) {
|
||||
|
||||
func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
|
||||
contextDir := createTestTempDir(t)
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
|
||||
chdir(t, contextDir)
|
||||
|
||||
@ -74,23 +74,23 @@ func TestGetContextFromLocalDirWithNoDirectory(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Check(t, is.Equal(contextDir, absContextDir))
|
||||
assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile))
|
||||
assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile))
|
||||
}
|
||||
|
||||
func TestGetContextFromLocalDirWithDockerfile(t *testing.T) {
|
||||
contextDir := createTestTempDir(t)
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
|
||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, "")
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Check(t, is.Equal(contextDir, absContextDir))
|
||||
assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile))
|
||||
assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile))
|
||||
}
|
||||
|
||||
func TestGetContextFromLocalDirLocalFile(t *testing.T) {
|
||||
contextDir := createTestTempDir(t)
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
testFilename := createTestTempFile(t, contextDir, "tmpTest", "test")
|
||||
|
||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(testFilename, "")
|
||||
@ -112,13 +112,13 @@ func TestGetContextFromLocalDirWithCustomDockerfile(t *testing.T) {
|
||||
contextDir := createTestTempDir(t)
|
||||
chdir(t, contextDir)
|
||||
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
|
||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, DefaultDockerfileName)
|
||||
absContextDir, relDockerfile, err := GetContextFromLocalDir(contextDir, defaultDockerfileName)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Check(t, is.Equal(contextDir, absContextDir))
|
||||
assert.Check(t, is.Equal(DefaultDockerfileName, relDockerfile))
|
||||
assert.Check(t, is.Equal(defaultDockerfileName, relDockerfile))
|
||||
}
|
||||
|
||||
func TestGetContextFromReaderString(t *testing.T) {
|
||||
@ -135,7 +135,7 @@ func TestGetContextFromReaderString(t *testing.T) {
|
||||
}
|
||||
|
||||
buff := new(bytes.Buffer)
|
||||
buff.ReadFrom(tarReader)
|
||||
_, _ = buff.ReadFrom(tarReader)
|
||||
contents := buff.String()
|
||||
|
||||
_, err = tarReader.Next()
|
||||
@ -150,8 +150,8 @@ func TestGetContextFromReaderString(t *testing.T) {
|
||||
t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents)
|
||||
}
|
||||
|
||||
if relDockerfile != DefaultDockerfileName {
|
||||
t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile)
|
||||
if relDockerfile != defaultDockerfileName {
|
||||
t.Fatalf("Relative path not equals %s, got: %s", defaultDockerfileName, relDockerfile)
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,12 +164,12 @@ func TestGetContextFromReaderStringConflict(t *testing.T) {
|
||||
|
||||
func TestGetContextFromReaderTar(t *testing.T) {
|
||||
contextDir := createTestTempDir(t)
|
||||
createTestTempFile(t, contextDir, DefaultDockerfileName, dockerfileContents)
|
||||
createTestTempFile(t, contextDir, defaultDockerfileName, dockerfileContents)
|
||||
|
||||
tarStream, err := archive.Tar(contextDir, compression.None)
|
||||
assert.NilError(t, err)
|
||||
|
||||
tarArchive, relDockerfile, err := GetContextFromReader(tarStream, DefaultDockerfileName)
|
||||
tarArchive, relDockerfile, err := GetContextFromReader(tarStream, defaultDockerfileName)
|
||||
assert.NilError(t, err)
|
||||
|
||||
tarReader := tar.NewReader(tarArchive)
|
||||
@ -177,12 +177,12 @@ func TestGetContextFromReaderTar(t *testing.T) {
|
||||
header, err := tarReader.Next()
|
||||
assert.NilError(t, err)
|
||||
|
||||
if header.Name != DefaultDockerfileName {
|
||||
t.Fatalf("Dockerfile name should be: %s, got: %s", DefaultDockerfileName, header.Name)
|
||||
if header.Name != defaultDockerfileName {
|
||||
t.Fatalf("Dockerfile name should be: %s, got: %s", defaultDockerfileName, header.Name)
|
||||
}
|
||||
|
||||
buff := new(bytes.Buffer)
|
||||
buff.ReadFrom(tarReader)
|
||||
_, _ = buff.ReadFrom(tarReader)
|
||||
contents := buff.String()
|
||||
|
||||
_, err = tarReader.Next()
|
||||
@ -197,8 +197,8 @@ func TestGetContextFromReaderTar(t *testing.T) {
|
||||
t.Fatalf("Uncompressed tar archive does not equal: %s, got: %s", dockerfileContents, contents)
|
||||
}
|
||||
|
||||
if relDockerfile != DefaultDockerfileName {
|
||||
t.Fatalf("Relative path not equals %s, got: %s", DefaultDockerfileName, relDockerfile)
|
||||
if relDockerfile != defaultDockerfileName {
|
||||
t.Fatalf("Relative path not equals %s, got: %s", defaultDockerfileName, relDockerfile)
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +223,7 @@ func TestValidateContextDirectoryWithOneFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateContextDirectoryWithOneFileExcludes(t *testing.T) {
|
||||
testValidateContextDirectory(t, prepareOneFile, []string{DefaultDockerfileName})
|
||||
testValidateContextDirectory(t, prepareOneFile, []string{defaultDockerfileName})
|
||||
}
|
||||
|
||||
// createTestTempDir creates a temporary directory for testing. It returns the
|
||||
@ -263,7 +263,7 @@ func chdir(t *testing.T, dir string) {
|
||||
}
|
||||
|
||||
func TestIsArchive(t *testing.T) {
|
||||
testcases := []struct {
|
||||
tests := []struct {
|
||||
doc string
|
||||
header []byte
|
||||
expected bool
|
||||
@ -289,13 +289,15 @@ func TestIsArchive(t *testing.T) {
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
assert.Check(t, is.Equal(testcase.expected, IsArchive(testcase.header)), testcase.doc)
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
assert.Check(t, is.Equal(tc.expected, isArchive(tc.header)), tc.doc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectArchiveReader(t *testing.T) {
|
||||
testcases := []struct {
|
||||
tests := []struct {
|
||||
file string
|
||||
desc string
|
||||
expected bool
|
||||
@ -316,14 +318,18 @@ func TestDetectArchiveReader(t *testing.T) {
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
content, err := os.Open(testcase.file)
|
||||
assert.NilError(t, err)
|
||||
defer content.Close()
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
content, err := os.Open(tc.file)
|
||||
assert.NilError(t, err)
|
||||
defer func() {
|
||||
_ = content.Close()
|
||||
}()
|
||||
|
||||
_, isArchive, err := DetectArchiveReader(content)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(testcase.expected, isArchive), testcase.file)
|
||||
_, isArchive, err := detectArchiveReader(content)
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(tc.expected, isArchive), tc.file)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,7 +21,9 @@ func ReadDockerignore(contextDir string) ([]string, error) {
|
||||
case err != nil:
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
defer func() {
|
||||
_ = f.Close()
|
||||
}()
|
||||
|
||||
patterns, err := ignorefile.ReadAll(f)
|
||||
if err != nil {
|
||||
|
||||
@ -2,15 +2,14 @@ package image
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -57,7 +56,7 @@ func newPullCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output")
|
||||
|
||||
addPlatformFlag(flags, &opts.platform)
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image verification")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||
|
||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
||||
|
||||
@ -87,15 +86,13 @@ func runPull(ctx context.Context, dockerCLI command.Cli, opts pullOptions) error
|
||||
// Check if reference has a digest
|
||||
_, isCanonical := distributionRef.(reference.Canonical)
|
||||
if !opts.untrusted && !isCanonical {
|
||||
err = trustedPull(ctx, dockerCLI, imgRefAndAuth, opts)
|
||||
} else {
|
||||
err = imagePullPrivileged(ctx, dockerCLI, imgRefAndAuth, opts)
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "when fetching 'plugin'") {
|
||||
return errors.New(err.Error() + " - Use `docker plugin install`")
|
||||
if err := trustedPull(ctx, dockerCLI, imgRefAndAuth, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := imagePullPrivileged(ctx, dockerCLI, imgRefAndAuth, opts); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCLI.Out(), imgRefAndAuth.Reference().String())
|
||||
return nil
|
||||
|
||||
@ -118,11 +118,12 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Setenv("DOCKER_CONTENT_TRUST", "true")
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePullFunc: func(ref string, options image.PullOptions) (io.ReadCloser, error) {
|
||||
return io.NopCloser(strings.NewReader("")), errors.New("shouldn't try to pull image")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
})
|
||||
cli.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := newPullCommand(cli)
|
||||
cmd.SetOut(io.Discard)
|
||||
|
||||
@ -15,12 +15,11 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/completion"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/cli/internal/jsonstream"
|
||||
"github.com/docker/cli/internal/registry"
|
||||
"github.com/docker/cli/internal/tui"
|
||||
"github.com/docker/docker/api/types/auxprogress"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/morikuni/aec"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
@ -64,7 +63,7 @@ func newPushCommand(dockerCLI command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Push all tags of an image to the repository")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress verbose output")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCLI.ContentTrustEnabled(), "Skip image signing")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image signing")
|
||||
|
||||
// Don't default to DOCKER_DEFAULT_PLATFORM env variable, always default to
|
||||
// pushing the image as-is. This also avoids forcing the platform selection
|
||||
@ -99,9 +98,11 @@ To push the complete multi-platform image, remove the --platform flag.
|
||||
}
|
||||
|
||||
ref, err := reference.ParseNormalizedNamed(opts.remote)
|
||||
switch {
|
||||
case err != nil:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch {
|
||||
case opts.all && !reference.IsNameOnly(ref):
|
||||
return errors.New("tag can't be used with --all-tags/-a")
|
||||
case !opts.all && reference.IsNameOnly(ref):
|
||||
@ -111,43 +112,37 @@ To push the complete multi-platform image, remove the --platform flag.
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve the Repository name from fqn to RepositoryInfo
|
||||
indexInfo := registry.NewIndexInfo(ref)
|
||||
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := command.ResolveAuthConfig(dockerCli.ConfigFile(), indexInfo)
|
||||
encodedAuth, err := registrytypes.EncodeAuthConfig(authConfig)
|
||||
encodedAuth, err := command.RetrieveAuthTokenFromImage(dockerCli.ConfigFile(), ref.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options := image.PushOptions{
|
||||
|
||||
responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), image.PushOptions{
|
||||
All: opts.all,
|
||||
RegistryAuth: encodedAuth,
|
||||
PrivilegeFunc: nil,
|
||||
Platform: platform,
|
||||
}
|
||||
|
||||
responseBody, err := dockerCli.Client().ImagePush(ctx, reference.FamiliarString(ref), options)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = responseBody.Close()
|
||||
for _, note := range notes {
|
||||
out.PrintNote(note)
|
||||
}
|
||||
}()
|
||||
|
||||
defer responseBody.Close()
|
||||
if !opts.untrusted {
|
||||
// TODO pushTrustedReference currently doesn't respect `--quiet`
|
||||
return pushTrustedReference(ctx, dockerCli, indexInfo, ref, authConfig, responseBody)
|
||||
return pushTrustedReference(ctx, dockerCli, ref, responseBody)
|
||||
}
|
||||
|
||||
if opts.quiet {
|
||||
err = jsonstream.Display(ctx, responseBody, streams.NewOut(io.Discard), jsonstream.WithAuxCallback(handleAux()))
|
||||
if err == nil {
|
||||
fmt.Fprintln(dockerCli.Out(), ref.String())
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), ref.String())
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import (
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/cli/internal/jsonstream"
|
||||
"github.com/docker/cli/internal/registry"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/opencontainers/go-digest"
|
||||
@ -42,12 +43,18 @@ func newNotaryClient(cli command.Streams, imgRefAndAuth trust.ImageRefAndAuth) (
|
||||
}
|
||||
|
||||
// pushTrustedReference pushes a canonical reference to the trust server.
|
||||
func pushTrustedReference(ctx context.Context, ioStreams command.Streams, indexInfo *registrytypes.IndexInfo, ref reference.Named, authConfig registrytypes.AuthConfig, in io.Reader) error {
|
||||
func pushTrustedReference(ctx context.Context, dockerCLI command.Cli, ref reference.Named, responseBody io.Reader) error {
|
||||
// Resolve the Repository name from fqn to RepositoryInfo, and create an
|
||||
// IndexInfo. Docker Content Trust uses the IndexInfo.Official field to
|
||||
// select the right domain for Docker Hub's Notary server;
|
||||
// https://github.com/docker/cli/blob/v28.4.0/cli/trust/trust.go#L65-L79
|
||||
indexInfo := registry.NewIndexInfo(ref)
|
||||
repoInfo := &trust.RepositoryInfo{
|
||||
Name: reference.TrimNamed(ref),
|
||||
Index: indexInfo,
|
||||
}
|
||||
return trust.PushTrustedReference(ctx, ioStreams, repoInfo, ref, authConfig, in, command.UserAgent())
|
||||
authConfig := command.ResolveAuthConfig(dockerCLI.ConfigFile(), indexInfo)
|
||||
return trust.PushTrustedReference(ctx, dockerCLI, repoInfo, ref, authConfig, responseBody, command.UserAgent())
|
||||
}
|
||||
|
||||
// trustedPull handles content trust pulling of an image
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/config"
|
||||
@ -54,6 +55,7 @@ func newRegistryClient(dockerCLI command.Cli, allowInsecure bool) registryclient
|
||||
resolver := func(ctx context.Context, index *registry.IndexInfo) registry.AuthConfig {
|
||||
return command.ResolveAuthConfig(dockerCLI.ConfigFile(), index)
|
||||
}
|
||||
// FIXME(thaJeztah): this should use the userAgent as configured on the dockerCLI.
|
||||
return registryclient.NewRegistryClient(resolver, command.UserAgent(), allowInsecure)
|
||||
}
|
||||
|
||||
@ -96,7 +98,7 @@ func runManifestAnnotate(dockerCLI command.Cli, opts annotateOptions) error {
|
||||
manifestStore := newManifestStore(dockerCLI)
|
||||
imageManifest, err := manifestStore.Get(targetRef, imgRef)
|
||||
switch {
|
||||
case store.IsNotFound(err):
|
||||
case errdefs.IsNotFound(err):
|
||||
return fmt.Errorf("manifest for image %s does not exist in %s", opts.image, opts.target)
|
||||
case err != nil:
|
||||
return err
|
||||
|
||||
@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/manifest/store"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -44,7 +44,7 @@ func createManifestList(ctx context.Context, dockerCLI command.Cli, args []strin
|
||||
manifestStore := newManifestStore(dockerCLI)
|
||||
_, err = manifestStore.GetList(targetRef)
|
||||
switch {
|
||||
case store.IsNotFound(err):
|
||||
case errdefs.IsNotFound(err):
|
||||
// New manifest list
|
||||
case err != nil:
|
||||
return err
|
||||
|
||||
@ -3,9 +3,9 @@ package manifest
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/manifest/store"
|
||||
"github.com/docker/cli/cli/manifest/types"
|
||||
)
|
||||
|
||||
@ -72,7 +72,7 @@ func normalizeReference(ref string) (reference.Named, error) {
|
||||
func getManifest(ctx context.Context, dockerCLI command.Cli, listRef, namedRef reference.Named, insecure bool) (types.ImageManifest, error) {
|
||||
data, err := newManifestStore(dockerCLI).Get(listRef, namedRef)
|
||||
switch {
|
||||
case store.IsNotFound(err):
|
||||
case errdefs.IsNotFound(err):
|
||||
return newRegistryClient(dockerCLI, insecure).GetManifest(ctx, namedRef)
|
||||
case err != nil:
|
||||
return types.ImageManifest{}, err
|
||||
|
||||
@ -3,12 +3,12 @@ package plugin
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/image"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/cli/internal/jsonstream"
|
||||
"github.com/docker/cli/internal/prompt"
|
||||
"github.com/docker/cli/internal/registry"
|
||||
@ -29,9 +29,9 @@ type pluginOptions struct {
|
||||
untrusted bool
|
||||
}
|
||||
|
||||
func loadPullFlags(dockerCli command.Cli, opts *pluginOptions, flags *pflag.FlagSet) {
|
||||
func loadPullFlags(opts *pluginOptions, flags *pflag.FlagSet) {
|
||||
flags.BoolVar(&opts.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image verification")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image verification")
|
||||
}
|
||||
|
||||
func newInstallCommand(dockerCli command.Cli) *cobra.Command {
|
||||
@ -50,7 +50,7 @@ func newInstallCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
loadPullFlags(dockerCli, &options, flags)
|
||||
loadPullFlags(&options, flags)
|
||||
flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install")
|
||||
flags.StringVar(&options.localName, "alias", "", "Local name for plugin")
|
||||
return cmd
|
||||
@ -120,9 +120,6 @@ func runInstall(ctx context.Context, dockerCLI command.Cli, opts pluginOptions)
|
||||
}
|
||||
responseBody, err := dockerCLI.Client().PluginInstall(ctx, localName, options)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "(image) when fetching") {
|
||||
return errors.New(err.Error() + " - Use \"docker image pull\"")
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@ -43,14 +43,6 @@ func TestInstallErrors(t *testing.T) {
|
||||
return nil, errors.New("error installing plugin")
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "installation error due to missing image",
|
||||
args: []string{"foo"},
|
||||
expectedError: "docker image pull",
|
||||
installFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
||||
return nil, errors.New("(image) when fetching")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -94,11 +86,12 @@ func TestInstallContentTrustErrors(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.description, func(t *testing.T) {
|
||||
t.Setenv("DOCKER_CONTENT_TRUST", "true")
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
pluginInstallFunc: func(name string, options types.PluginInstallOptions) (io.ReadCloser, error) {
|
||||
return nil, errors.New("should not try to install plugin")
|
||||
},
|
||||
}, test.EnableContentTrust)
|
||||
})
|
||||
cli.SetNotaryClient(tc.notaryFunc)
|
||||
cmd := newInstallCommand(cli)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
@ -33,7 +33,7 @@ func newPushCommand(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !dockerCli.ContentTrustEnabled(), "Skip image signing")
|
||||
flags.BoolVar(&opts.untrusted, "disable-content-trust", !trust.Enabled(), "Skip image signing")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package plugin
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli"
|
||||
@ -31,7 +30,7 @@ func newUpgradeCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
loadPullFlags(dockerCli, &options, flags)
|
||||
loadPullFlags(&options, flags)
|
||||
flags.BoolVar(&options.skipRemoteCheck, "skip-remote-check", false, "Do not check if specified remote plugin matches existing plugin image")
|
||||
return cmd
|
||||
}
|
||||
@ -80,9 +79,6 @@ func runUpgrade(ctx context.Context, dockerCLI command.Cli, opts pluginOptions)
|
||||
|
||||
responseBody, err := dockerCLI.Client().PluginUpgrade(ctx, opts.localName, options)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "target is image") {
|
||||
return errors.New(err.Error() + " - Use `docker image pull`")
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
|
||||
@ -77,7 +77,16 @@ func ResolveAuthConfig(cfg *configfile.ConfigFile, index *registrytypes.IndexInf
|
||||
}
|
||||
|
||||
a, _ := cfg.GetAuthConfig(configKey)
|
||||
return registrytypes.AuthConfig(a)
|
||||
return registrytypes.AuthConfig{
|
||||
Username: a.Username,
|
||||
Password: a.Password,
|
||||
ServerAddress: a.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: a.Auth,
|
||||
IdentityToken: a.IdentityToken,
|
||||
RegistryToken: a.RegistryToken,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDefaultAuthConfig gets the default auth config given a serverAddress
|
||||
@ -86,19 +95,27 @@ func GetDefaultAuthConfig(cfg *configfile.ConfigFile, checkCredStore bool, serve
|
||||
if !isDefaultRegistry {
|
||||
serverAddress = credentials.ConvertToHostname(serverAddress)
|
||||
}
|
||||
authconfig := configtypes.AuthConfig{}
|
||||
authCfg := configtypes.AuthConfig{}
|
||||
var err error
|
||||
if checkCredStore {
|
||||
authconfig, err = cfg.GetAuthConfig(serverAddress)
|
||||
authCfg, err = cfg.GetAuthConfig(serverAddress)
|
||||
if err != nil {
|
||||
return registrytypes.AuthConfig{
|
||||
ServerAddress: serverAddress,
|
||||
}, err
|
||||
}
|
||||
}
|
||||
authconfig.ServerAddress = serverAddress
|
||||
authconfig.IdentityToken = ""
|
||||
return registrytypes.AuthConfig(authconfig), nil
|
||||
|
||||
return registrytypes.AuthConfig{
|
||||
Username: authCfg.Username,
|
||||
Password: authCfg.Password,
|
||||
ServerAddress: serverAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authCfg.Auth,
|
||||
IdentityToken: "",
|
||||
RegistryToken: authCfg.RegistryToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PromptUserForCredentials handles the CLI prompt for the user to input
|
||||
@ -213,7 +230,16 @@ func RetrieveAuthTokenFromImage(cfg *configfile.ConfigFile, image string) (strin
|
||||
return "", err
|
||||
}
|
||||
|
||||
encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig))
|
||||
encodedAuth, err := registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -259,12 +259,30 @@ func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg st
|
||||
return "", err
|
||||
}
|
||||
|
||||
response, err := loginWithRegistry(ctx, dockerCLI.Client(), registrytypes.AuthConfig(*authConfig))
|
||||
response, err := loginWithRegistry(ctx, dockerCLI.Client(), registrytypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err = storeCredentials(dockerCLI.ConfigFile(), registrytypes.AuthConfig(*authConfig)); err != nil {
|
||||
if err = storeCredentials(dockerCLI.ConfigFile(), registrytypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -273,7 +291,16 @@ func loginWithDeviceCodeFlow(ctx context.Context, dockerCLI command.Cli) (msg st
|
||||
|
||||
func storeCredentials(cfg *configfile.ConfigFile, authConfig registrytypes.AuthConfig) error {
|
||||
creds := cfg.GetCredentialsStore(authConfig.ServerAddress)
|
||||
if err := creds.Store(configtypes.AuthConfig(authConfig)); err != nil {
|
||||
if err := creds.Store(configtypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
}); err != nil {
|
||||
return errors.Errorf("Error saving credentials: %v", err)
|
||||
}
|
||||
|
||||
|
||||
@ -102,7 +102,16 @@ func getAuth(dockerCLI command.Cli, reposName string) (encodedAuth string, err e
|
||||
// "no credentials found"). We'll get an error when search failed,
|
||||
// so fine to ignore in most situations.
|
||||
authConfig, _ := dockerCLI.ConfigFile().GetAuthConfig(authCfgKey)
|
||||
return registrytypes.EncodeAuthConfig(registrytypes.AuthConfig(authConfig))
|
||||
return registrytypes.EncodeAuthConfig(registrytypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
})
|
||||
}
|
||||
|
||||
// splitReposSearchTerm breaks a search term into an index name and remote name
|
||||
|
||||
@ -58,8 +58,17 @@ func TestGetDefaultAuthConfig(t *testing.T) {
|
||||
},
|
||||
}
|
||||
cfg := configfile.New("filename")
|
||||
for _, authconfig := range testAuthConfigs {
|
||||
assert.Check(t, cfg.GetCredentialsStore(authconfig.ServerAddress).Store(configtypes.AuthConfig(authconfig)))
|
||||
for _, authConfig := range testAuthConfigs {
|
||||
assert.Check(t, cfg.GetCredentialsStore(authConfig.ServerAddress).Store(configtypes.AuthConfig{
|
||||
Username: authConfig.Username,
|
||||
Password: authConfig.Password,
|
||||
ServerAddress: authConfig.ServerAddress,
|
||||
|
||||
// TODO(thaJeztah): Are these expected to be included?
|
||||
Auth: authConfig.Auth,
|
||||
IdentityToken: authConfig.IdentityToken,
|
||||
RegistryToken: authConfig.RegistryToken,
|
||||
}))
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
serverAddress := tc.inputServerAddress
|
||||
|
||||
@ -15,7 +15,7 @@ import (
|
||||
)
|
||||
|
||||
func resolveServiceImageDigestContentTrust(dockerCli command.Cli, service *swarm.ServiceSpec) error {
|
||||
if !dockerCli.ContentTrustEnabled() {
|
||||
if !trust.Enabled() {
|
||||
// When not using content trust, digest resolution happens later when
|
||||
// contacting the registry to retrieve image information.
|
||||
return nil
|
||||
|
||||
@ -168,7 +168,7 @@ func needsServerInfo(template string, info dockerInfo) bool {
|
||||
}
|
||||
|
||||
// A template is provided and has at least one field set.
|
||||
tmpl, err := templates.NewParse("", template)
|
||||
tmpl, err := templates.Parse(template)
|
||||
if err != nil {
|
||||
// ignore parsing errors here, and let regular code handle them
|
||||
return true
|
||||
|
||||
@ -215,8 +215,7 @@ func newVersionTemplate(templateFormat string) (*template.Template, error) {
|
||||
case formatter.JSONFormatKey:
|
||||
templateFormat = formatter.JSONFormat
|
||||
}
|
||||
tmpl := templates.New("version").Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder})
|
||||
tmpl, err := tmpl.Parse(templateFormat)
|
||||
tmpl, err := templates.New("version").Funcs(template.FuncMap{"getDetailsOrder": getDetailsOrder}).Parse(templateFormat)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "template parsing error")
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
@ -116,7 +117,11 @@ func toServicePortConfigsMap(s any) (map[any]any, error) {
|
||||
}
|
||||
m := map[any]any{}
|
||||
for _, p := range ports {
|
||||
m[p.Published] = p
|
||||
protocol := "tcp"
|
||||
if p.Protocol != "" {
|
||||
protocol = p.Protocol
|
||||
}
|
||||
m[fmt.Sprintf("%d%s", p.Published, protocol)] = p
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
@ -848,6 +848,8 @@ func TestLoadMultipleConfigs(t *testing.T) {
|
||||
"ports": []any{
|
||||
"8080:80",
|
||||
"9090:90",
|
||||
"53:53/tcp",
|
||||
"53:53/udp",
|
||||
},
|
||||
"labels": []any{
|
||||
"foo=bar",
|
||||
@ -925,6 +927,18 @@ func TestLoadMultipleConfigs(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Ports: []types.ServicePortConfig{
|
||||
{
|
||||
Mode: "ingress",
|
||||
Target: 53,
|
||||
Published: 53,
|
||||
Protocol: "tcp",
|
||||
},
|
||||
{
|
||||
Mode: "ingress",
|
||||
Target: 53,
|
||||
Published: 53,
|
||||
Protocol: "udp",
|
||||
},
|
||||
{
|
||||
Target: 81,
|
||||
Published: 8080,
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
package memorystore
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"os"
|
||||
@ -13,12 +12,17 @@ import (
|
||||
"github.com/docker/cli/cli/config/types"
|
||||
)
|
||||
|
||||
var errValueNotFound = errors.New("value not found")
|
||||
// notFoundErr is the error returned when a plugin could not be found.
|
||||
type notFoundErr string
|
||||
|
||||
func IsErrValueNotFound(err error) bool {
|
||||
return errors.Is(err, errValueNotFound)
|
||||
func (notFoundErr) NotFound() {}
|
||||
|
||||
func (e notFoundErr) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
var errValueNotFound notFoundErr = "value not found"
|
||||
|
||||
type Config struct {
|
||||
lock sync.RWMutex
|
||||
memoryCredentials map[string]types.AuthConfig
|
||||
|
||||
@ -101,7 +101,22 @@ func (ep *Endpoint) ClientOpts() ([]client.Opt, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, withHTTPClient(tlsConfig))
|
||||
|
||||
// If there's no tlsConfig available, we use the default HTTPClient.
|
||||
if tlsConfig != nil {
|
||||
result = append(result,
|
||||
client.WithHTTPClient(&http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
DialContext: (&net.Dialer{
|
||||
KeepAlive: 30 * time.Second,
|
||||
Timeout: 30 * time.Second,
|
||||
}).DialContext,
|
||||
},
|
||||
CheckRedirect: client.CheckRedirect,
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
result = append(result, client.WithHost(ep.Host))
|
||||
} else {
|
||||
@ -133,25 +148,6 @@ func isSocket(addr string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func withHTTPClient(tlsConfig *tls.Config) func(*client.Client) error {
|
||||
return func(c *client.Client) error {
|
||||
if tlsConfig == nil {
|
||||
// Use the default HTTPClient
|
||||
return nil
|
||||
}
|
||||
return client.WithHTTPClient(&http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
DialContext: (&net.Dialer{
|
||||
KeepAlive: 30 * time.Second,
|
||||
Timeout: 30 * time.Second,
|
||||
}).DialContext,
|
||||
},
|
||||
CheckRedirect: client.CheckRedirect,
|
||||
})(c)
|
||||
}
|
||||
}
|
||||
|
||||
// EndpointFromContext parses a context docker endpoint metadata into a typed EndpointMeta structure
|
||||
func EndpointFromContext(metadata store.Metadata) (EndpointMeta, error) {
|
||||
ep, ok := metadata.Endpoints[DockerEndpoint]
|
||||
|
||||
@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/manifest/types"
|
||||
"github.com/docker/distribution/manifest/manifestlist"
|
||||
@ -152,27 +153,13 @@ func makeFilesafeName(ref string) string {
|
||||
return strings.ReplaceAll(fileName, "/", "_")
|
||||
}
|
||||
|
||||
type notFoundError struct {
|
||||
object string
|
||||
func newNotFoundError(ref string) error {
|
||||
return errdefs.ErrNotFound.WithMessage("No such manifest: " + ref)
|
||||
}
|
||||
|
||||
func newNotFoundError(ref string) *notFoundError {
|
||||
return ¬FoundError{object: ref}
|
||||
}
|
||||
|
||||
func (n *notFoundError) Error() string {
|
||||
return "No such manifest: " + n.object
|
||||
}
|
||||
|
||||
// NotFound interface
|
||||
func (*notFoundError) NotFound() {}
|
||||
|
||||
// IsNotFound returns true if the error is a not found error
|
||||
//
|
||||
// Deprecated: use [errdefs.IsNotFound]. This function will be removed in the next release.
|
||||
func IsNotFound(err error) bool {
|
||||
_, ok := err.(notFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
type notFound interface {
|
||||
NotFound()
|
||||
return errdefs.IsNotFound(err)
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/errdefs"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/manifest/types"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@ -86,7 +87,7 @@ func TestStoreSaveAndGet(t *testing.T) {
|
||||
actual, err := store.Get(tc.listRef, tc.manifestRef)
|
||||
if tc.expectedErr != "" {
|
||||
assert.Error(t, err, tc.expectedErr)
|
||||
assert.Check(t, IsNotFound(err))
|
||||
assert.Check(t, errdefs.IsNotFound(err))
|
||||
return
|
||||
}
|
||||
assert.NilError(t, err)
|
||||
@ -117,5 +118,5 @@ func TestStoreGetListDoesNotExist(t *testing.T) {
|
||||
listRef := ref("list")
|
||||
_, err := store.GetList(listRef)
|
||||
assert.Error(t, err, "No such manifest: list")
|
||||
assert.Check(t, IsNotFound(err))
|
||||
assert.Check(t, errdefs.IsNotFound(err))
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package trust
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
@ -10,6 +11,7 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
@ -42,6 +44,20 @@ var (
|
||||
ActionsPushAndPull = []string{"pull", "push"}
|
||||
)
|
||||
|
||||
// Enabled returns whether content-trust is enabled through the DOCKER_CONTENT_TRUST env-var.
|
||||
//
|
||||
// IMPORTANT: this function is for internal use, and may be removed at any moment.
|
||||
func Enabled() bool {
|
||||
var enabled bool
|
||||
if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" {
|
||||
if t, err := strconv.ParseBool(e); t || err != nil {
|
||||
// treat any other value as true
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
return enabled
|
||||
}
|
||||
|
||||
// NotaryServer is the endpoint serving the Notary trust server
|
||||
const NotaryServer = "https://notary.docker.io"
|
||||
|
||||
@ -92,6 +108,11 @@ func (scs simpleCredentialStore) RefreshToken(*url.URL, string) string {
|
||||
|
||||
func (simpleCredentialStore) SetRefreshToken(*url.URL, string, string) {}
|
||||
|
||||
const dctDeprecation = `WARNING: Docker is retiring DCT for Docker Official Images (DOI).
|
||||
For details, refer to https://docs.docker.com/go/dct-deprecation/
|
||||
|
||||
`
|
||||
|
||||
// GetNotaryRepository returns a NotaryRepository which stores all the
|
||||
// information needed to operate on a notary repository.
|
||||
// It creates an HTTP transport providing authentication support.
|
||||
@ -100,6 +121,9 @@ func GetNotaryRepository(in io.Reader, out io.Writer, userAgent string, repoInfo
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if server == NotaryServer {
|
||||
_, _ = fmt.Fprint(os.Stderr, dctDeprecation)
|
||||
}
|
||||
|
||||
cfg := tlsconfig.ClientDefault()
|
||||
cfg.InsecureSkipVerify = !repoInfo.Index.Secure
|
||||
@ -327,7 +351,10 @@ func GetImageReferencesAndAuth(ctx context.Context,
|
||||
return ImageRefAndAuth{}, err
|
||||
}
|
||||
|
||||
// Resolve the Repository name from fqn to RepositoryInfo
|
||||
// Resolve the Repository name from fqn to RepositoryInfo, and create an
|
||||
// IndexInfo. Docker Content Trust uses the IndexInfo.Official field to
|
||||
// select the right domain for Docker Hub's Notary server;
|
||||
// https://github.com/docker/cli/blob/v28.4.0/cli/trust/trust.go#L65-L79
|
||||
indexInfo := registry.NewIndexInfo(ref)
|
||||
authConfig := authResolver(ctx, indexInfo)
|
||||
return ImageRefAndAuth{
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
variable "GO_VERSION" {
|
||||
default = "1.24.7"
|
||||
default = "1.24.9"
|
||||
}
|
||||
variable "VERSION" {
|
||||
default = ""
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.24.7
|
||||
ARG GO_VERSION=1.24.9
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
@ -28,7 +28,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
FROM golang AS gotestsum
|
||||
# GOTESTSUM_VERSION sets the version of gotestsum to install in the dev container.
|
||||
# It must be a valid tag in the https://github.com/gotestyourself/gotestsum repository.
|
||||
ARG GOTESTSUM_VERSION=v1.12.3
|
||||
ARG GOTESTSUM_VERSION=v1.13.0
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
--mount=type=tmpfs,target=/go/src/ \
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.24.7
|
||||
ARG GO_VERSION=1.24.9
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
# that's also available as alpine image variant for the Golang version used.
|
||||
ARG ALPINE_VERSION=3.21
|
||||
ARG GOLANGCI_LINT_VERSION=v2.1.5
|
||||
# GOLANGCI_LINT_VERSION sets the version of the golangci/golangci-lint image to use.
|
||||
ARG GOLANGCI_LINT_VERSION=v2.5.0
|
||||
|
||||
FROM golangci/golangci-lint:${GOLANGCI_LINT_VERSION}-alpine AS golangci-lint
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.24.7
|
||||
ARG GO_VERSION=1.24.9
|
||||
|
||||
# ALPINE_VERSION sets the version of the alpine base image to use, including for the golang image.
|
||||
# It must be a supported tag in the docker.io/library/alpine image repository
|
||||
|
||||
@ -44,8 +44,7 @@ func SetupConfigWithNotaryURL(t *testing.T, path, notaryURL string) fs.Dir {
|
||||
"%s": {
|
||||
"auth": "ZWlhaXM6cGFzc3dvcmQK"
|
||||
}
|
||||
},
|
||||
"experimental": "enabled"
|
||||
}
|
||||
}
|
||||
`, notaryURL)), fs.WithDir("trust", fs.WithDir("private")))
|
||||
return *dir
|
||||
|
||||
4
e2e/testdata/Dockerfile.connhelper-ssh
vendored
4
e2e/testdata/Dockerfile.connhelper-ssh
vendored
@ -7,8 +7,8 @@ ARG ENGINE_VERSION=28
|
||||
FROM docker:${ENGINE_VERSION}-dind
|
||||
|
||||
# the openssh-client update is needed for security reasons when using docker:23.0-dind, currently maintained as an lts by mirantis
|
||||
RUN apk --no-cache upgrade openssh-client && \
|
||||
apk --no-cache add shadow openssh-server && \
|
||||
RUN apk --no-cache add openssl openssh-client openssh-server shadow && \
|
||||
apk --no-cache upgrade openssl openssh-client openssh-server && \
|
||||
# TODO(krissetto): `groupadd` can be removed once we only test against moby >= v24
|
||||
# see https://github.com/docker-library/docker/pull/470
|
||||
groupadd -f docker && \
|
||||
|
||||
2
e2e/testdata/Dockerfile.gencerts
vendored
2
e2e/testdata/Dockerfile.gencerts
vendored
@ -1,6 +1,6 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.24.7
|
||||
ARG GO_VERSION=1.24.9
|
||||
|
||||
FROM golang:${GO_VERSION}-alpine AS generated
|
||||
ENV GOTOOLCHAIN=local
|
||||
|
||||
@ -36,7 +36,6 @@ type FakeCli struct {
|
||||
notaryClientFunc NotaryClientFuncType
|
||||
manifestStore manifeststore.Store
|
||||
registryClient registryclient.RegistryClient
|
||||
contentTrust bool
|
||||
contextStore store.Store
|
||||
currentContext string
|
||||
dockerEndpoint docker.Endpoint
|
||||
@ -198,16 +197,6 @@ func (c *FakeCli) SetRegistryClient(registryClient registryclient.RegistryClient
|
||||
c.registryClient = registryClient
|
||||
}
|
||||
|
||||
// ContentTrustEnabled on the fake cli
|
||||
func (c *FakeCli) ContentTrustEnabled() bool {
|
||||
return c.contentTrust
|
||||
}
|
||||
|
||||
// EnableContentTrust on the fake cli
|
||||
func EnableContentTrust(c *FakeCli) {
|
||||
c.contentTrust = true
|
||||
}
|
||||
|
||||
// BuildKitEnabled on the fake cli
|
||||
func (*FakeCli) BuildKitEnabled() (bool, error) {
|
||||
return true, nil
|
||||
|
||||
@ -60,6 +60,8 @@ func (opts *ListOpts) Set(value string) error {
|
||||
}
|
||||
|
||||
// Delete removes the specified element from the slice.
|
||||
//
|
||||
// Deprecated: this method is no longer used and will be removed in the next release.
|
||||
func (opts *ListOpts) Delete(key string) {
|
||||
for i, k := range *opts.values {
|
||||
if k == key {
|
||||
@ -264,6 +266,8 @@ func ValidateIPAddress(val string) (string, error) {
|
||||
}
|
||||
|
||||
// ValidateMACAddress validates a MAC address.
|
||||
//
|
||||
// Deprecated: use [net.ParseMAC]. This function will be removed in the next release.
|
||||
func ValidateMACAddress(val string) (string, error) {
|
||||
_, err := net.ParseMAC(strings.TrimSpace(val))
|
||||
if err != nil {
|
||||
|
||||
@ -142,18 +142,14 @@ func TestListOptsWithoutValidator(t *testing.T) {
|
||||
if o.Get("baz") {
|
||||
t.Error(`o.Get("baz") == true`)
|
||||
}
|
||||
o.Delete("foo")
|
||||
if o.String() != "[bar bar]" {
|
||||
t.Errorf("%s != [bar bar]", o.String())
|
||||
if listOpts := o.GetSlice(); len(listOpts) != 3 || listOpts[0] != "foo" || listOpts[1] != "bar" || listOpts[2] != "bar" {
|
||||
t.Errorf("Expected [[foo bar bar]], got [%v]", listOpts)
|
||||
}
|
||||
if listOpts := o.GetAll(); len(listOpts) != 2 || listOpts[0] != "bar" || listOpts[1] != "bar" {
|
||||
t.Errorf("Expected [[bar bar]], got [%v]", listOpts)
|
||||
if listOpts := o.GetAll(); len(listOpts) != 3 || listOpts[0] != "foo" || listOpts[1] != "bar" || listOpts[2] != "bar" {
|
||||
t.Errorf("Expected [[foo bar bar]], got [%v]", listOpts)
|
||||
}
|
||||
if listOpts := o.GetSlice(); len(listOpts) != 2 || listOpts[0] != "bar" || listOpts[1] != "bar" {
|
||||
t.Errorf("Expected [[bar bar]], got [%v]", listOpts)
|
||||
}
|
||||
if mapListOpts := o.GetMap(); len(mapListOpts) != 1 {
|
||||
t.Errorf("Expected [map[bar:{}]], got [%v]", mapListOpts)
|
||||
if mapListOpts := o.GetMap(); len(mapListOpts) != 2 {
|
||||
t.Errorf("Expected [map[bar:{} foo:{}]], got [%v]", mapListOpts)
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,9 +182,8 @@ func TestListOptsWithValidator(t *testing.T) {
|
||||
if o.Get("baz") {
|
||||
t.Error(`o.Get("baz") == true`)
|
||||
}
|
||||
o.Delete("valid-option2=2")
|
||||
if o.String() != "" {
|
||||
t.Errorf(`%s != ""`, o.String())
|
||||
if expected := "[valid-option2=2]"; o.String() != expected {
|
||||
t.Errorf(`%s != %q`, o.String(), expected)
|
||||
}
|
||||
}
|
||||
|
||||
@ -396,20 +391,6 @@ func TestNamedMapOpts(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMACAddress(t *testing.T) {
|
||||
if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil {
|
||||
t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err)
|
||||
}
|
||||
|
||||
if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil {
|
||||
t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC")
|
||||
}
|
||||
|
||||
if _, err := ValidateMACAddress(`random invalid string`); err == nil {
|
||||
t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateLink(t *testing.T) {
|
||||
valid := []string{
|
||||
"name",
|
||||
|
||||
@ -309,7 +309,8 @@ func TestPortOptInvalidSimpleSyntax(t *testing.T) {
|
||||
},
|
||||
{
|
||||
value: "",
|
||||
expectedError: "no port specified: <empty>",
|
||||
expectedError: "invalid proto: ",
|
||||
// expectedError: "no port specified: <empty>", // FIXME(thaJeztah): re-enable once https://github.com/docker/go-connections/pull/143 is in a go-connections release.
|
||||
},
|
||||
{
|
||||
value: "1.1.1.1:80:80",
|
||||
|
||||
@ -71,7 +71,7 @@ var HeaderFunctions = template.FuncMap{
|
||||
// Parse creates a new anonymous template with the basic functions
|
||||
// and parses the given format.
|
||||
func Parse(format string) (*template.Template, error) {
|
||||
return NewParse("", format)
|
||||
return template.New("").Funcs(basicFunctions).Parse(format)
|
||||
}
|
||||
|
||||
// New creates a new empty template with the provided tag and built-in
|
||||
@ -82,8 +82,10 @@ func New(tag string) *template.Template {
|
||||
|
||||
// NewParse creates a new tagged template with the basic functions
|
||||
// and parses the given format.
|
||||
//
|
||||
// Deprecated: this function is unused and will be removed in the next release. Use [New] if you need to set a tag, or [Parse] instead.
|
||||
func NewParse(tag, format string) (*template.Template, error) {
|
||||
return New(tag).Parse(format)
|
||||
return template.New(tag).Funcs(basicFunctions).Parse(format)
|
||||
}
|
||||
|
||||
// padWithSpace adds whitespace to the input if the input is non-empty
|
||||
|
||||
@ -30,7 +30,7 @@ func TestParseStringFunctions(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewParse(t *testing.T) {
|
||||
tm, err := NewParse("foo", "this is a {{ . }}")
|
||||
tm, err := New("foo").Parse("this is a {{ . }}")
|
||||
assert.NilError(t, err)
|
||||
|
||||
var b bytes.Buffer
|
||||
|
||||
@ -16,9 +16,9 @@ require (
|
||||
github.com/distribution/reference v0.6.0
|
||||
github.com/docker/cli-docs-tool v0.10.0
|
||||
github.com/docker/distribution v2.8.3+incompatible
|
||||
github.com/docker/docker v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible // 28.4.0-dev
|
||||
github.com/docker/docker v28.5.1+incompatible
|
||||
github.com/docker/docker-credential-helpers v0.9.3
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/docker/go-connections v0.6.0
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/fvbommel/sortorder v1.1.0
|
||||
github.com/go-jose/go-jose/v4 v4.0.5
|
||||
|
||||
@ -57,15 +57,15 @@ github.com/docker/cli-docs-tool v0.10.0/go.mod h1:5EM5zPnT2E7yCLERZmrDA234Vwn09f
|
||||
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 v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible h1:Pkl6clGEMTFjRD57vOF36IXOeqaURCc7OOz5iAiRY3U=
|
||||
github.com/docker/docker v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM=
|
||||
github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32 h1:EHZfspsnLAz8Hzccd67D5abwLiqoqym2jz/jOS39mCk=
|
||||
github.com/docker/go-events v0.0.0-20250114142523-c867878c5e32/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
||||
|
||||
95
vendor/github.com/docker/docker/api/swagger.yaml
generated
vendored
95
vendor/github.com/docker/docker/api/swagger.yaml
generated
vendored
@ -81,7 +81,6 @@ info:
|
||||
{
|
||||
"username": "string",
|
||||
"password": "string",
|
||||
"email": "string",
|
||||
"serveraddress": "string"
|
||||
}
|
||||
```
|
||||
@ -637,6 +636,9 @@ definitions:
|
||||
by the default (runc) runtime.
|
||||
|
||||
This field is omitted when empty.
|
||||
|
||||
**Deprecated**: This field is deprecated as kernel 6.12 has deprecated `memory.kmem.tcp.limit_in_bytes` field
|
||||
for cgroups v1. This field will be removed in a future release.
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
MemoryReservation:
|
||||
@ -1531,37 +1533,6 @@ definitions:
|
||||
items:
|
||||
type: "string"
|
||||
example: ["/bin/sh", "-c"]
|
||||
# FIXME(thaJeztah): temporarily using a full example to remove some "omitempty" fields. Remove once the fields are removed.
|
||||
example:
|
||||
"User": "web:web"
|
||||
"ExposedPorts": {
|
||||
"80/tcp": {},
|
||||
"443/tcp": {}
|
||||
}
|
||||
"Env": ["PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"]
|
||||
"Cmd": ["/bin/sh"]
|
||||
"Healthcheck": {
|
||||
"Test": ["string"],
|
||||
"Interval": 0,
|
||||
"Timeout": 0,
|
||||
"Retries": 0,
|
||||
"StartPeriod": 0,
|
||||
"StartInterval": 0
|
||||
}
|
||||
"ArgsEscaped": true
|
||||
"Volumes": {
|
||||
"/app/data": {},
|
||||
"/app/config": {}
|
||||
}
|
||||
"WorkingDir": "/public/"
|
||||
"Entrypoint": []
|
||||
"OnBuild": []
|
||||
"Labels": {
|
||||
"com.example.some-label": "some-value",
|
||||
"com.example.some-other-label": "some-other-value"
|
||||
}
|
||||
"StopSignal": "SIGTERM"
|
||||
"Shell": ["/bin/sh", "-c"]
|
||||
|
||||
NetworkingConfig:
|
||||
description: |
|
||||
@ -1967,6 +1938,11 @@ definitions:
|
||||
Depending on how the image was created, this field may be empty and
|
||||
is only set for images that were built/created locally. This field
|
||||
is empty if the image was pulled from an image registry.
|
||||
|
||||
> **Deprecated**: This field is only set when using the deprecated
|
||||
> legacy builder. It is included in API responses for informational
|
||||
> purposes, but should not be depended on as it will be omitted
|
||||
> once the legacy builder is removed.
|
||||
type: "string"
|
||||
x-nullable: false
|
||||
example: ""
|
||||
@ -1992,6 +1968,11 @@ definitions:
|
||||
The version of Docker that was used to build the image.
|
||||
|
||||
Depending on how the image was created, this field may be empty.
|
||||
|
||||
> **Deprecated**: This field is only set when using the deprecated
|
||||
> legacy builder. It is included in API responses for informational
|
||||
> purposes, but should not be depended on as it will be omitted
|
||||
> once the legacy builder is removed.
|
||||
type: "string"
|
||||
x-nullable: false
|
||||
example: "27.0.1"
|
||||
@ -2036,14 +2017,6 @@ definitions:
|
||||
format: "int64"
|
||||
x-nullable: false
|
||||
example: 1239828
|
||||
VirtualSize:
|
||||
description: |
|
||||
Total size of the image including all layers it is composed of.
|
||||
|
||||
Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead.
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
example: 1239828
|
||||
GraphDriver:
|
||||
$ref: "#/definitions/DriverData"
|
||||
RootFS:
|
||||
@ -2176,14 +2149,6 @@ definitions:
|
||||
format: "int64"
|
||||
x-nullable: false
|
||||
example: 1239828
|
||||
VirtualSize:
|
||||
description: |-
|
||||
Total size of the image including all layers it is composed of.
|
||||
|
||||
Deprecated: this field is omitted in API v1.44, but kept for backward compatibility. Use Size instead.
|
||||
type: "integer"
|
||||
format: "int64"
|
||||
example: 172064416
|
||||
Labels:
|
||||
description: "User-defined key/value metadata."
|
||||
type: "object"
|
||||
@ -3177,10 +3142,15 @@ definitions:
|
||||
- Args
|
||||
properties:
|
||||
DockerVersion:
|
||||
description: "Docker Version used to create the plugin"
|
||||
description: |-
|
||||
Docker Version used to create the plugin.
|
||||
|
||||
Depending on how the plugin was created, this field may be empty or omitted.
|
||||
|
||||
Deprecated: this field is no longer set, and will be removed in the next API version.
|
||||
type: "string"
|
||||
x-nullable: false
|
||||
example: "17.06.0-ce"
|
||||
x-omitempty: true
|
||||
Description:
|
||||
type: "string"
|
||||
x-nullable: false
|
||||
@ -6382,6 +6352,8 @@ definitions:
|
||||
|
||||
Kernel memory TCP limits are not supported when using cgroups v2, which
|
||||
does not support the corresponding `memory.kmem.tcp.limit_in_bytes` cgroup.
|
||||
|
||||
**Deprecated**: This field is deprecated as kernel 6.12 has deprecated kernel memory TCP accounting.
|
||||
type: "boolean"
|
||||
example: true
|
||||
CpuCfsPeriod:
|
||||
@ -6419,29 +6391,6 @@ definitions:
|
||||
description: "Indicates IPv4 forwarding is enabled."
|
||||
type: "boolean"
|
||||
example: true
|
||||
BridgeNfIptables:
|
||||
description: |
|
||||
Indicates if `bridge-nf-call-iptables` is available on the host when
|
||||
the daemon was started.
|
||||
|
||||
<p><br /></p>
|
||||
|
||||
> **Deprecated**: netfilter module is now loaded on-demand and no longer
|
||||
> during daemon startup, making this field obsolete. This field is always
|
||||
> `false` and will be removed in a API v1.49.
|
||||
type: "boolean"
|
||||
example: false
|
||||
BridgeNfIp6tables:
|
||||
description: |
|
||||
Indicates if `bridge-nf-call-ip6tables` is available on the host.
|
||||
|
||||
<p><br /></p>
|
||||
|
||||
> **Deprecated**: netfilter module is now loaded on-demand, and no longer
|
||||
> during daemon startup, making this field obsolete. This field is always
|
||||
> `false` and will be removed in a API v1.49.
|
||||
type: "boolean"
|
||||
example: false
|
||||
Debug:
|
||||
description: |
|
||||
Indicates if the daemon is running in debug-mode / with debug-level
|
||||
|
||||
7
vendor/github.com/docker/docker/api/types/container/hostconfig.go
generated
vendored
7
vendor/github.com/docker/docker/api/types/container/hostconfig.go
generated
vendored
@ -394,7 +394,12 @@ type Resources struct {
|
||||
|
||||
// KernelMemory specifies the kernel memory limit (in bytes) for the container.
|
||||
// Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes.
|
||||
KernelMemory int64 `json:",omitempty"`
|
||||
KernelMemory int64 `json:",omitempty"`
|
||||
// Hard limit for kernel TCP buffer memory (in bytes).
|
||||
//
|
||||
// Deprecated: This field is deprecated and will be removed in the next release.
|
||||
// Starting with 6.12, the kernel has deprecated kernel memory tcp accounting
|
||||
// for cgroups v1.
|
||||
KernelMemoryTCP int64 `json:",omitempty"` // Hard limit for kernel TCP buffer memory (in bytes)
|
||||
MemoryReservation int64 // Memory soft limit (in bytes)
|
||||
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap
|
||||
|
||||
4
vendor/github.com/docker/docker/api/types/image/image_inspect.go
generated
vendored
4
vendor/github.com/docker/docker/api/types/image/image_inspect.go
generated
vendored
@ -48,6 +48,8 @@ type InspectResponse struct {
|
||||
// Depending on how the image was created, this field may be empty and
|
||||
// is only set for images that were built/created locally. This field
|
||||
// is empty if the image was pulled from an image registry.
|
||||
//
|
||||
// Deprecated: this field is deprecated, and will be removed in the next release.
|
||||
Parent string
|
||||
|
||||
// Comment is an optional message that can be set when committing or
|
||||
@ -80,6 +82,8 @@ type InspectResponse struct {
|
||||
// DockerVersion is the version of Docker that was used to build the image.
|
||||
//
|
||||
// Depending on how the image was created, this field may be empty.
|
||||
//
|
||||
// Deprecated: this field is deprecated, and will be removed in the next release.
|
||||
DockerVersion string
|
||||
|
||||
// Author is the name of the author that was specified when committing the
|
||||
|
||||
6
vendor/github.com/docker/docker/api/types/plugin.go
generated
vendored
6
vendor/github.com/docker/docker/api/types/plugin.go
generated
vendored
@ -42,7 +42,11 @@ type PluginConfig struct {
|
||||
// Required: true
|
||||
Description string `json:"Description"`
|
||||
|
||||
// Docker Version used to create the plugin
|
||||
// Docker Version used to create the plugin.
|
||||
//
|
||||
// Depending on how the plugin was created, this field may be empty or omitted.
|
||||
//
|
||||
// Deprecated: this field is no longer set, and will be removed in the next API version.
|
||||
DockerVersion string `json:"DockerVersion,omitempty"`
|
||||
|
||||
// documentation
|
||||
|
||||
30
vendor/github.com/docker/docker/api/types/system/info.go
generated
vendored
30
vendor/github.com/docker/docker/api/types/system/info.go
generated
vendored
@ -9,19 +9,23 @@ import (
|
||||
// Info contains response of Engine API:
|
||||
// GET "/info"
|
||||
type Info struct {
|
||||
ID string
|
||||
Containers int
|
||||
ContainersRunning int
|
||||
ContainersPaused int
|
||||
ContainersStopped int
|
||||
Images int
|
||||
Driver string
|
||||
DriverStatus [][2]string
|
||||
SystemStatus [][2]string `json:",omitempty"` // SystemStatus is only propagated by the Swarm standalone API
|
||||
Plugins PluginsInfo
|
||||
MemoryLimit bool
|
||||
SwapLimit bool
|
||||
KernelMemory bool `json:",omitempty"` // Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes
|
||||
ID string
|
||||
Containers int
|
||||
ContainersRunning int
|
||||
ContainersPaused int
|
||||
ContainersStopped int
|
||||
Images int
|
||||
Driver string
|
||||
DriverStatus [][2]string
|
||||
SystemStatus [][2]string `json:",omitempty"` // SystemStatus is only propagated by the Swarm standalone API
|
||||
Plugins PluginsInfo
|
||||
MemoryLimit bool
|
||||
SwapLimit bool
|
||||
KernelMemory bool `json:",omitempty"` // Deprecated: kernel 5.4 deprecated kmem.limit_in_bytes
|
||||
// KernelMemoryLimit is not supported on cgroups v2.
|
||||
//
|
||||
// Deprecated: This field is deprecated and will be removed in the next release.
|
||||
// Starting with kernel 6.12, the kernel has deprecated kernel memory tcp accounting
|
||||
KernelMemoryTCP bool `json:",omitempty"` // KernelMemoryTCP is not supported on cgroups v2.
|
||||
CPUCfsPeriod bool `json:"CpuCfsPeriod"`
|
||||
CPUCfsQuota bool `json:"CpuCfsQuota"`
|
||||
|
||||
153
vendor/github.com/docker/go-connections/nat/nat.go
generated
vendored
153
vendor/github.com/docker/go-connections/nat/nat.go
generated
vendored
@ -2,6 +2,7 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
@ -43,19 +44,19 @@ func NewPort(proto, port string) (Port, error) {
|
||||
|
||||
// ParsePort parses the port number string and returns an int
|
||||
func ParsePort(rawPort string) (int, error) {
|
||||
if len(rawPort) == 0 {
|
||||
if rawPort == "" {
|
||||
return 0, nil
|
||||
}
|
||||
port, err := strconv.ParseUint(rawPort, 10, 16)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, fmt.Errorf("invalid port '%s': %w", rawPort, errors.Unwrap(err))
|
||||
}
|
||||
return int(port), nil
|
||||
}
|
||||
|
||||
// ParsePortRangeToInt parses the port range string and returns start/end ints
|
||||
func ParsePortRangeToInt(rawPort string) (int, int, error) {
|
||||
if len(rawPort) == 0 {
|
||||
if rawPort == "" {
|
||||
return 0, 0, nil
|
||||
}
|
||||
start, end, err := ParsePortRange(rawPort)
|
||||
@ -91,29 +92,31 @@ func (p Port) Range() (int, int, error) {
|
||||
return ParsePortRangeToInt(p.Port())
|
||||
}
|
||||
|
||||
// SplitProtoPort splits a port in the format of proto/port
|
||||
func SplitProtoPort(rawPort string) (string, string) {
|
||||
parts := strings.Split(rawPort, "/")
|
||||
l := len(parts)
|
||||
if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 {
|
||||
// SplitProtoPort splits a port(range) and protocol, formatted as "<portnum>/[<proto>]"
|
||||
// "<startport-endport>/[<proto>]". It returns an empty string for both if
|
||||
// no port(range) is provided. If a port(range) is provided, but no protocol,
|
||||
// the default ("tcp") protocol is returned.
|
||||
//
|
||||
// SplitProtoPort does not validate or normalize the returned values.
|
||||
func SplitProtoPort(rawPort string) (proto string, port string) {
|
||||
port, proto, _ = strings.Cut(rawPort, "/")
|
||||
if port == "" {
|
||||
return "", ""
|
||||
}
|
||||
if l == 1 {
|
||||
return "tcp", rawPort
|
||||
if proto == "" {
|
||||
proto = "tcp"
|
||||
}
|
||||
if len(parts[1]) == 0 {
|
||||
return "tcp", parts[0]
|
||||
}
|
||||
return parts[1], parts[0]
|
||||
return proto, port
|
||||
}
|
||||
|
||||
func validateProto(proto string) bool {
|
||||
for _, availableProto := range []string{"tcp", "udp", "sctp"} {
|
||||
if availableProto == proto {
|
||||
return true
|
||||
}
|
||||
func validateProto(proto string) error {
|
||||
switch proto {
|
||||
case "tcp", "udp", "sctp":
|
||||
// All good
|
||||
return nil
|
||||
default:
|
||||
return errors.New("invalid proto: " + proto)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ParsePortSpecs receives port specs in the format of ip:public:private/proto and parses
|
||||
@ -123,22 +126,18 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
|
||||
exposedPorts = make(map[Port]struct{}, len(ports))
|
||||
bindings = make(map[Port][]PortBinding)
|
||||
)
|
||||
for _, rawPort := range ports {
|
||||
portMappings, err := ParsePortSpec(rawPort)
|
||||
for _, p := range ports {
|
||||
portMappings, err := ParsePortSpec(p)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
for _, portMapping := range portMappings {
|
||||
port := portMapping.Port
|
||||
if _, exists := exposedPorts[port]; !exists {
|
||||
for _, pm := range portMappings {
|
||||
port := pm.Port
|
||||
if _, ok := exposedPorts[port]; !ok {
|
||||
exposedPorts[port] = struct{}{}
|
||||
}
|
||||
bslice, exists := bindings[port]
|
||||
if !exists {
|
||||
bslice = []PortBinding{}
|
||||
}
|
||||
bindings[port] = append(bslice, portMapping.Binding)
|
||||
bindings[port] = append(bindings[port], pm.Binding)
|
||||
}
|
||||
}
|
||||
return exposedPorts, bindings, nil
|
||||
@ -150,28 +149,34 @@ type PortMapping struct {
|
||||
Binding PortBinding
|
||||
}
|
||||
|
||||
func splitParts(rawport string) (string, string, string) {
|
||||
parts := strings.Split(rawport, ":")
|
||||
n := len(parts)
|
||||
containerPort := parts[n-1]
|
||||
func (p *PortMapping) String() string {
|
||||
return net.JoinHostPort(p.Binding.HostIP, p.Binding.HostPort+":"+string(p.Port))
|
||||
}
|
||||
|
||||
switch n {
|
||||
func splitParts(rawport string) (hostIP, hostPort, containerPort string) {
|
||||
parts := strings.Split(rawport, ":")
|
||||
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
return "", "", containerPort
|
||||
return "", "", parts[0]
|
||||
case 2:
|
||||
return "", parts[0], containerPort
|
||||
return "", parts[0], parts[1]
|
||||
case 3:
|
||||
return parts[0], parts[1], containerPort
|
||||
return parts[0], parts[1], parts[2]
|
||||
default:
|
||||
return strings.Join(parts[:n-2], ":"), parts[n-2], containerPort
|
||||
n := len(parts)
|
||||
return strings.Join(parts[:n-2], ":"), parts[n-2], parts[n-1]
|
||||
}
|
||||
}
|
||||
|
||||
// ParsePortSpec parses a port specification string into a slice of PortMappings
|
||||
func ParsePortSpec(rawPort string) ([]PortMapping, error) {
|
||||
var proto string
|
||||
ip, hostPort, containerPort := splitParts(rawPort)
|
||||
proto, containerPort = SplitProtoPort(containerPort)
|
||||
proto, containerPort := SplitProtoPort(containerPort)
|
||||
proto = strings.ToLower(proto)
|
||||
if err := validateProto(proto); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ip != "" && ip[0] == '[' {
|
||||
// Strip [] from IPV6 addresses
|
||||
@ -182,7 +187,7 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) {
|
||||
ip = rawIP
|
||||
}
|
||||
if ip != "" && net.ParseIP(ip) == nil {
|
||||
return nil, fmt.Errorf("invalid IP address: %s", ip)
|
||||
return nil, errors.New("invalid IP address: " + ip)
|
||||
}
|
||||
if containerPort == "" {
|
||||
return nil, fmt.Errorf("no port specified: %s<empty>", rawPort)
|
||||
@ -190,51 +195,43 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) {
|
||||
|
||||
startPort, endPort, err := ParsePortRange(containerPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid containerPort: %s", containerPort)
|
||||
return nil, errors.New("invalid containerPort: " + containerPort)
|
||||
}
|
||||
|
||||
var startHostPort, endHostPort uint64 = 0, 0
|
||||
if len(hostPort) > 0 {
|
||||
var startHostPort, endHostPort uint64
|
||||
if hostPort != "" {
|
||||
startHostPort, endHostPort, err = ParsePortRange(hostPort)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hostPort: %s", hostPort)
|
||||
return nil, errors.New("invalid hostPort: " + hostPort)
|
||||
}
|
||||
if (endPort - startPort) != (endHostPort - startHostPort) {
|
||||
// Allow host port range iff containerPort is not a range.
|
||||
// In this case, use the host port range as the dynamic
|
||||
// host port range to allocate into.
|
||||
if endPort != startPort {
|
||||
return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
|
||||
// Allow host port range iff containerPort is not a range.
|
||||
// In this case, use the host port range as the dynamic
|
||||
// host port range to allocate into.
|
||||
if endPort != startPort {
|
||||
return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
|
||||
}
|
||||
}
|
||||
count := endPort - startPort + 1
|
||||
ports := make([]PortMapping, 0, count)
|
||||
|
||||
if !validateProto(strings.ToLower(proto)) {
|
||||
return nil, fmt.Errorf("invalid proto: %s", proto)
|
||||
}
|
||||
|
||||
ports := []PortMapping{}
|
||||
for i := uint64(0); i <= (endPort - startPort); i++ {
|
||||
containerPort = strconv.FormatUint(startPort+i, 10)
|
||||
if len(hostPort) > 0 {
|
||||
hostPort = strconv.FormatUint(startHostPort+i, 10)
|
||||
for i := uint64(0); i < count; i++ {
|
||||
cPort := Port(strconv.FormatUint(startPort+i, 10) + "/" + proto)
|
||||
hPort := ""
|
||||
if hostPort != "" {
|
||||
hPort = strconv.FormatUint(startHostPort+i, 10)
|
||||
// Set hostPort to a range only if there is a single container port
|
||||
// and a dynamic host port.
|
||||
if count == 1 && startHostPort != endHostPort {
|
||||
hPort += "-" + strconv.FormatUint(endHostPort, 10)
|
||||
}
|
||||
}
|
||||
// Set hostPort to a range only if there is a single container port
|
||||
// and a dynamic host port.
|
||||
if startPort == endPort && startHostPort != endHostPort {
|
||||
hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10))
|
||||
}
|
||||
port, err := NewPort(strings.ToLower(proto), containerPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
binding := PortBinding{
|
||||
HostIP: ip,
|
||||
HostPort: hostPort,
|
||||
}
|
||||
ports = append(ports, PortMapping{Port: port, Binding: binding})
|
||||
ports = append(ports, PortMapping{
|
||||
Port: cPort,
|
||||
Binding: PortBinding{HostIP: ip, HostPort: hPort},
|
||||
})
|
||||
}
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
6
vendor/github.com/docker/go-connections/nat/parse.go
generated
vendored
6
vendor/github.com/docker/go-connections/nat/parse.go
generated
vendored
@ -1,7 +1,7 @@
|
||||
package nat
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@ -9,7 +9,7 @@ import (
|
||||
// ParsePortRange parses and validates the specified string as a port-range (8000-9000)
|
||||
func ParsePortRange(ports string) (uint64, uint64, error) {
|
||||
if ports == "" {
|
||||
return 0, 0, fmt.Errorf("empty string specified for ports")
|
||||
return 0, 0, errors.New("empty string specified for ports")
|
||||
}
|
||||
if !strings.Contains(ports, "-") {
|
||||
start, err := strconv.ParseUint(ports, 10, 16)
|
||||
@ -27,7 +27,7 @@ func ParsePortRange(ports string) (uint64, uint64, error) {
|
||||
return 0, 0, err
|
||||
}
|
||||
if end < start {
|
||||
return 0, 0, fmt.Errorf("invalid range specified for port: %s", ports)
|
||||
return 0, 0, errors.New("invalid range specified for port: " + ports)
|
||||
}
|
||||
return start, end, nil
|
||||
}
|
||||
|
||||
0
vendor/github.com/docker/go-connections/sockets/README.md
generated
vendored
0
vendor/github.com/docker/go-connections/sockets/README.md
generated
vendored
9
vendor/github.com/docker/go-connections/sockets/proxy.go
generated
vendored
9
vendor/github.com/docker/go-connections/sockets/proxy.go
generated
vendored
@ -9,6 +9,8 @@ import (
|
||||
// GetProxyEnv allows access to the uppercase and the lowercase forms of
|
||||
// proxy-related variables. See the Go specification for details on these
|
||||
// variables. https://golang.org/pkg/net/http/
|
||||
//
|
||||
// Deprecated: this function was used as helper for [DialerFromEnvironment] and is no longer used. It will be removed in the next release.
|
||||
func GetProxyEnv(key string) string {
|
||||
proxyValue := os.Getenv(strings.ToUpper(key))
|
||||
if proxyValue == "" {
|
||||
@ -19,10 +21,11 @@ func GetProxyEnv(key string) string {
|
||||
|
||||
// DialerFromEnvironment was previously used to configure a net.Dialer to route
|
||||
// connections through a SOCKS proxy.
|
||||
// DEPRECATED: SOCKS proxies are now supported by configuring only
|
||||
//
|
||||
// Deprecated: SOCKS proxies are now supported by configuring only
|
||||
// http.Transport.Proxy, and no longer require changing http.Transport.Dial.
|
||||
// Therefore, only sockets.ConfigureTransport() needs to be called, and any
|
||||
// sockets.DialerFromEnvironment() calls can be dropped.
|
||||
// Therefore, only [sockets.ConfigureTransport] needs to be called, and any
|
||||
// [sockets.DialerFromEnvironment] calls can be dropped.
|
||||
func DialerFromEnvironment(direct *net.Dialer) (*net.Dialer, error) {
|
||||
return direct, nil
|
||||
}
|
||||
|
||||
31
vendor/github.com/docker/go-connections/sockets/sockets.go
generated
vendored
31
vendor/github.com/docker/go-connections/sockets/sockets.go
generated
vendored
@ -2,13 +2,19 @@
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const defaultTimeout = 10 * time.Second
|
||||
const (
|
||||
defaultTimeout = 10 * time.Second
|
||||
maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
|
||||
)
|
||||
|
||||
// ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system.
|
||||
var ErrProtocolNotAvailable = errors.New("protocol not available")
|
||||
@ -35,3 +41,26 @@ func ConfigureTransport(tr *http.Transport, proto, addr string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DialPipe connects to a Windows named pipe. It is not supported on
|
||||
// non-Windows platforms.
|
||||
//
|
||||
// Deprecated: use [github.com/Microsoft/go-winio.DialPipe] or [github.com/Microsoft/go-winio.DialPipeContext].
|
||||
func DialPipe(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return dialPipe(addr, timeout)
|
||||
}
|
||||
|
||||
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
|
||||
if len(addr) > maxUnixSocketPathSize {
|
||||
return fmt.Errorf("unix socket path %q is too long", addr)
|
||||
}
|
||||
// No need for compression in local communications.
|
||||
tr.DisableCompression = true
|
||||
dialer := &net.Dialer{
|
||||
Timeout: defaultTimeout,
|
||||
}
|
||||
tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, proto, addr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
23
vendor/github.com/docker/go-connections/sockets/sockets_unix.go
generated
vendored
23
vendor/github.com/docker/go-connections/sockets/sockets_unix.go
generated
vendored
@ -3,37 +3,16 @@
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
|
||||
|
||||
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
|
||||
if len(addr) > maxUnixSocketPathSize {
|
||||
return fmt.Errorf("unix socket path %q is too long", addr)
|
||||
}
|
||||
// No need for compression in local communications.
|
||||
tr.DisableCompression = true
|
||||
dialer := &net.Dialer{
|
||||
Timeout: defaultTimeout,
|
||||
}
|
||||
tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, proto, addr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
|
||||
return ErrProtocolNotAvailable
|
||||
}
|
||||
|
||||
// DialPipe connects to a Windows named pipe.
|
||||
// This is not supported on other OSes.
|
||||
func DialPipe(_ string, _ time.Duration) (net.Conn, error) {
|
||||
func dialPipe(_ string, _ time.Duration) (net.Conn, error) {
|
||||
return nil, syscall.EAFNOSUPPORT
|
||||
}
|
||||
|
||||
7
vendor/github.com/docker/go-connections/sockets/sockets_windows.go
generated
vendored
7
vendor/github.com/docker/go-connections/sockets/sockets_windows.go
generated
vendored
@ -9,10 +9,6 @@ import (
|
||||
"github.com/Microsoft/go-winio"
|
||||
)
|
||||
|
||||
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
|
||||
return ErrProtocolNotAvailable
|
||||
}
|
||||
|
||||
func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
|
||||
// No need for compression in local communications.
|
||||
tr.DisableCompression = true
|
||||
@ -22,7 +18,6 @@ func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DialPipe connects to a Windows named pipe.
|
||||
func DialPipe(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
func dialPipe(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return winio.DialPipe(addr, &timeout)
|
||||
}
|
||||
|
||||
44
vendor/github.com/docker/go-connections/sockets/unix_socket.go
generated
vendored
44
vendor/github.com/docker/go-connections/sockets/unix_socket.go
generated
vendored
@ -1,5 +1,3 @@
|
||||
//go:build !windows
|
||||
|
||||
/*
|
||||
Package sockets is a simple unix domain socket wrapper.
|
||||
|
||||
@ -57,26 +55,6 @@ import (
|
||||
// SockOption sets up socket file's creating option
|
||||
type SockOption func(string) error
|
||||
|
||||
// WithChown modifies the socket file's uid and gid
|
||||
func WithChown(uid, gid int) SockOption {
|
||||
return func(path string) error {
|
||||
if err := os.Chown(path, uid, gid); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithChmod modifies socket file's access mode.
|
||||
func WithChmod(mask os.FileMode) SockOption {
|
||||
return func(path string) error {
|
||||
if err := os.Chmod(path, mask); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewUnixSocketWithOpts creates a unix socket with the specified options.
|
||||
// By default, socket permissions are 0000 (i.e.: no access for anyone); pass
|
||||
// WithChmod() and WithChown() to set the desired ownership and permissions.
|
||||
@ -90,22 +68,7 @@ func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// net.Listen does not allow for permissions to be set. As a result, when
|
||||
// specifying custom permissions ("WithChmod()"), there is a short time
|
||||
// between creating the socket and applying the permissions, during which
|
||||
// the socket permissions are Less restrictive than desired.
|
||||
//
|
||||
// To work around this limitation of net.Listen(), we temporarily set the
|
||||
// umask to 0777, which forces the socket to be created with 000 permissions
|
||||
// (i.e.: no access for anyone). After that, WithChmod() must be used to set
|
||||
// the desired permissions.
|
||||
//
|
||||
// We don't use "defer" here, to reset the umask to its original value as soon
|
||||
// as possible. Ideally we'd be able to detect if WithChmod() was passed as
|
||||
// an option, and skip changing umask if default permissions are used.
|
||||
origUmask := syscall.Umask(0o777)
|
||||
l, err := net.Listen("unix", path)
|
||||
syscall.Umask(origUmask)
|
||||
l, err := listenUnix(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -119,8 +82,3 @@ func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// NewUnixSocket creates a unix socket with the specified path and group.
|
||||
func NewUnixSocket(path string, gid int) (net.Listener, error) {
|
||||
return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0o660))
|
||||
}
|
||||
|
||||
54
vendor/github.com/docker/go-connections/sockets/unix_socket_unix.go
generated
vendored
Normal file
54
vendor/github.com/docker/go-connections/sockets/unix_socket_unix.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
//go:build !windows
|
||||
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// WithChown modifies the socket file's uid and gid
|
||||
func WithChown(uid, gid int) SockOption {
|
||||
return func(path string) error {
|
||||
if err := os.Chown(path, uid, gid); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithChmod modifies socket file's access mode.
|
||||
func WithChmod(mask os.FileMode) SockOption {
|
||||
return func(path string) error {
|
||||
if err := os.Chmod(path, mask); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewUnixSocket creates a unix socket with the specified path and group.
|
||||
func NewUnixSocket(path string, gid int) (net.Listener, error) {
|
||||
return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0o660))
|
||||
}
|
||||
|
||||
func listenUnix(path string) (net.Listener, error) {
|
||||
// net.Listen does not allow for permissions to be set. As a result, when
|
||||
// specifying custom permissions ("WithChmod()"), there is a short time
|
||||
// between creating the socket and applying the permissions, during which
|
||||
// the socket permissions are Less restrictive than desired.
|
||||
//
|
||||
// To work around this limitation of net.Listen(), we temporarily set the
|
||||
// umask to 0777, which forces the socket to be created with 000 permissions
|
||||
// (i.e.: no access for anyone). After that, WithChmod() must be used to set
|
||||
// the desired permissions.
|
||||
//
|
||||
// We don't use "defer" here, to reset the umask to its original value as soon
|
||||
// as possible. Ideally we'd be able to detect if WithChmod() was passed as
|
||||
// an option, and skip changing umask if default permissions are used.
|
||||
origUmask := syscall.Umask(0o777)
|
||||
l, err := net.Listen("unix", path)
|
||||
syscall.Umask(origUmask)
|
||||
return l, err
|
||||
}
|
||||
7
vendor/github.com/docker/go-connections/sockets/unix_socket_windows.go
generated
vendored
Normal file
7
vendor/github.com/docker/go-connections/sockets/unix_socket_windows.go
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
package sockets
|
||||
|
||||
import "net"
|
||||
|
||||
func listenUnix(path string) (net.Listener, error) {
|
||||
return net.Listen("unix", path)
|
||||
}
|
||||
100
vendor/github.com/docker/go-connections/tlsconfig/config.go
generated
vendored
100
vendor/github.com/docker/go-connections/tlsconfig/config.go
generated
vendored
@ -34,51 +34,37 @@ type Options struct {
|
||||
// the system pool will be used.
|
||||
ExclusiveRootPools bool
|
||||
MinVersion uint16
|
||||
// If Passphrase is set, it will be used to decrypt a TLS private key
|
||||
// if the key is encrypted.
|
||||
//
|
||||
// Deprecated: Use of encrypted TLS private keys has been deprecated, and
|
||||
// will be removed in a future release. Golang has deprecated support for
|
||||
// legacy PEM encryption (as specified in RFC 1423), as it is insecure by
|
||||
// design (see https://go-review.googlesource.com/c/go/+/264159).
|
||||
Passphrase string
|
||||
}
|
||||
|
||||
// Extra (server-side) accepted CBC cipher suites - will phase out in the future
|
||||
var acceptedCBCCiphers = []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
|
||||
}
|
||||
|
||||
// DefaultServerAcceptedCiphers should be uses by code which already has a crypto/tls
|
||||
// options struct but wants to use a commonly accepted set of TLS cipher suites, with
|
||||
// known weak algorithms removed.
|
||||
var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...)
|
||||
var DefaultServerAcceptedCiphers = defaultCipherSuites
|
||||
|
||||
// defaultCipherSuites is shared by both client and server as the default set.
|
||||
var defaultCipherSuites = []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
}
|
||||
|
||||
// ServerDefault returns a secure-enough TLS configuration for the server TLS configuration.
|
||||
func ServerDefault(ops ...func(*tls.Config)) *tls.Config {
|
||||
tlsConfig := &tls.Config{
|
||||
// Avoid fallback by default to SSL protocols < TLS1.2
|
||||
MinVersion: tls.VersionTLS12,
|
||||
PreferServerCipherSuites: true,
|
||||
CipherSuites: DefaultServerAcceptedCiphers,
|
||||
}
|
||||
|
||||
for _, op := range ops {
|
||||
op(tlsConfig)
|
||||
}
|
||||
|
||||
return tlsConfig
|
||||
return defaultConfig(ops...)
|
||||
}
|
||||
|
||||
// ClientDefault returns a secure-enough TLS configuration for the client TLS configuration.
|
||||
func ClientDefault(ops ...func(*tls.Config)) *tls.Config {
|
||||
return defaultConfig(ops...)
|
||||
}
|
||||
|
||||
// defaultConfig is the default config used by both client and server TLS configuration.
|
||||
func defaultConfig(ops ...func(*tls.Config)) *tls.Config {
|
||||
tlsConfig := &tls.Config{
|
||||
// Prefer TLS1.2 as the client minimum
|
||||
// Avoid fallback by default to SSL protocols < TLS1.2
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CipherSuites: clientCipherSuites,
|
||||
CipherSuites: defaultCipherSuites,
|
||||
}
|
||||
|
||||
for _, op := range ops {
|
||||
@ -92,13 +78,13 @@ func ClientDefault(ops ...func(*tls.Config)) *tls.Config {
|
||||
func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) {
|
||||
// If we should verify the server, we need to load a trusted ca
|
||||
var (
|
||||
certPool *x509.CertPool
|
||||
err error
|
||||
pool *x509.CertPool
|
||||
err error
|
||||
)
|
||||
if exclusivePool {
|
||||
certPool = x509.NewCertPool()
|
||||
pool = x509.NewCertPool()
|
||||
} else {
|
||||
certPool, err = SystemCertPool()
|
||||
pool, err = SystemCertPool()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read system certificates: %v", err)
|
||||
}
|
||||
@ -107,10 +93,10 @@ func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read CA certificate %q: %v", caFile, err)
|
||||
}
|
||||
if !certPool.AppendCertsFromPEM(pemData) {
|
||||
if !pool.AppendCertsFromPEM(pemData) {
|
||||
return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile)
|
||||
}
|
||||
return certPool, nil
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// allTLSVersions lists all the TLS versions and is used by the code that validates
|
||||
@ -144,34 +130,32 @@ func adjustMinVersion(options Options, config *tls.Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsErrEncryptedKey returns true if the 'err' is an error of incorrect
|
||||
// password when trying to decrypt a TLS private key.
|
||||
// errEncryptedKeyDeprecated is produced when we encounter an encrypted
|
||||
// (password-protected) key. From https://go-review.googlesource.com/c/go/+/264159;
|
||||
//
|
||||
// Deprecated: Use of encrypted TLS private keys has been deprecated, and
|
||||
// will be removed in a future release. Golang has deprecated support for
|
||||
// legacy PEM encryption (as specified in RFC 1423), as it is insecure by
|
||||
// design (see https://go-review.googlesource.com/c/go/+/264159).
|
||||
func IsErrEncryptedKey(err error) bool {
|
||||
return errors.Is(err, x509.IncorrectPasswordError)
|
||||
}
|
||||
// > Legacy PEM encryption as specified in RFC 1423 is insecure by design. Since
|
||||
// > it does not authenticate the ciphertext, it is vulnerable to padding oracle
|
||||
// > attacks that can let an attacker recover the plaintext
|
||||
// >
|
||||
// > It's unfortunate that we don't implement PKCS#8 encryption so we can't
|
||||
// > recommend an alternative but PEM encryption is so broken that it's worth
|
||||
// > deprecating outright.
|
||||
//
|
||||
// Also see https://docs.docker.com/go/deprecated/
|
||||
var errEncryptedKeyDeprecated = errors.New("private key is encrypted; encrypted private keys are obsolete, and not supported")
|
||||
|
||||
// getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format.
|
||||
// If the private key is encrypted, 'passphrase' is used to decrypted the
|
||||
// private key.
|
||||
func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) {
|
||||
// It returns an error if the file could not be decoded or was protected by
|
||||
// a passphrase.
|
||||
func getPrivateKey(keyBytes []byte) ([]byte, error) {
|
||||
// this section makes some small changes to code from notary/tuf/utils/x509.go
|
||||
pemBlock, _ := pem.Decode(keyBytes)
|
||||
if pemBlock == nil {
|
||||
return nil, fmt.Errorf("no valid private key found")
|
||||
}
|
||||
|
||||
var err error
|
||||
if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // Ignore SA1019 (IsEncryptedPEMBlock is deprecated)
|
||||
keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase)) //nolint:staticcheck // Ignore SA1019 (DecryptPEMBlock is deprecated)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("private key is encrypted, but could not decrypt it: %w", err)
|
||||
}
|
||||
keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes})
|
||||
return nil, errEncryptedKeyDeprecated
|
||||
}
|
||||
|
||||
return keyBytes, nil
|
||||
@ -195,7 +179,7 @@ func getCert(options Options) ([]tls.Certificate, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase)
|
||||
prKeyBytes, err = getPrivateKey(prKeyBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -210,7 +194,7 @@ func getCert(options Options) ([]tls.Certificate, error) {
|
||||
|
||||
// Client returns a TLS configuration meant to be used by a client.
|
||||
func Client(options Options) (*tls.Config, error) {
|
||||
tlsConfig := ClientDefault()
|
||||
tlsConfig := defaultConfig()
|
||||
tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify
|
||||
if !options.InsecureSkipVerify && options.CAFile != "" {
|
||||
CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
|
||||
@ -235,7 +219,7 @@ func Client(options Options) (*tls.Config, error) {
|
||||
|
||||
// Server returns a TLS configuration meant to be used by a server.
|
||||
func Server(options Options) (*tls.Config, error) {
|
||||
tlsConfig := ServerDefault()
|
||||
tlsConfig := defaultConfig()
|
||||
tlsConfig.ClientAuth = options.ClientAuth
|
||||
tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
|
||||
if err != nil {
|
||||
|
||||
14
vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go
generated
vendored
14
vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go
generated
vendored
@ -1,14 +0,0 @@
|
||||
// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers.
|
||||
package tlsconfig
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
)
|
||||
|
||||
// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set)
|
||||
var clientCipherSuites = []uint16{
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
}
|
||||
4
vendor/modules.txt
vendored
4
vendor/modules.txt
vendored
@ -65,7 +65,7 @@ github.com/docker/distribution/registry/client/transport
|
||||
github.com/docker/distribution/registry/storage/cache
|
||||
github.com/docker/distribution/registry/storage/cache/memory
|
||||
github.com/docker/distribution/uuid
|
||||
# github.com/docker/docker v28.4.0-rc.2.0.20250903202849-249d679a6baf+incompatible
|
||||
# github.com/docker/docker v28.5.1+incompatible
|
||||
## explicit
|
||||
github.com/docker/docker/api
|
||||
github.com/docker/docker/api/types
|
||||
@ -101,7 +101,7 @@ github.com/docker/docker-credential-helpers/credentials
|
||||
# github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c
|
||||
## explicit
|
||||
github.com/docker/go/canonical/json
|
||||
# github.com/docker/go-connections v0.5.0
|
||||
# github.com/docker/go-connections v0.6.0
|
||||
## explicit; go 1.18
|
||||
github.com/docker/go-connections/nat
|
||||
github.com/docker/go-connections/sockets
|
||||
|
||||
Reference in New Issue
Block a user