mirror of https://github.com/docker/cli.git
Compare commits
39 Commits
ba92f12a7d
...
cc5ea7c176
Author | SHA1 | Date |
---|---|---|
Way2go | cc5ea7c176 | |
Sebastiaan van Stijn | 9861ce90fd | |
Sebastiaan van Stijn | d1d5353269 | |
Sebastiaan van Stijn | 4adbb18f1c | |
Sebastiaan van Stijn | e00ed82399 | |
Sebastiaan van Stijn | 3dd7621240 | |
Sebastiaan van Stijn | 4242cda826 | |
Sebastiaan van Stijn | a4228409d2 | |
Sebastiaan van Stijn | 7c80e4f938 | |
Harald Albers | 06260e68f3 | |
Harald Albers | 4525fe37b4 | |
Harald Albers | db0ed1e216 | |
Harald Albers | 2915749279 | |
Harald Albers | 3a2503fa43 | |
Harald Albers | 9a9ae231a9 | |
Harald Albers | 5f7c43e5e6 | |
Harald Albers | 3292afe6e6 | |
Harald Albers | 5d709a8d9f | |
Harald Albers | 2d89339b34 | |
Harald Albers | ac7bde6f64 | |
Harald Albers | e513454244 | |
Harald Albers | c555327f0b | |
Harald Albers | b598ec8cdb | |
Harald Albers | 761d76750c | |
Sebastiaan van Stijn | 917d2dc837 | |
Sebastiaan van Stijn | f9497b8a46 | |
Paweł Gronowski | 382d4c34a9 | |
aevesdocker | 1440f9f8cf | |
Sebastiaan van Stijn | 9c01d924fb | |
David Karlsson | 172f340112 | |
Wayne Cheng | b039c0e9af | |
Wayne Cheng | 46b474267b | |
Wayne Cheng | c2919580b6 | |
Wayne Cheng | 6fc8567ce6 | |
Wayne Cheng | 3ecd392a8c | |
Wayne Cheng | dbd460a216 | |
Wayne Cheng | 402a3400f0 | |
Wayne Cheng | 72ef456387 | |
Wayne Cheng | 6bff05f02f |
|
@ -62,7 +62,7 @@ jobs:
|
||||||
name: Update Go
|
name: Update Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.23.2
|
go-version: 1.23.3
|
||||||
-
|
-
|
||||||
name: Initialize CodeQL
|
name: Initialize CodeQL
|
||||||
uses: github/codeql-action/init@v3
|
uses: github/codeql-action/init@v3
|
||||||
|
|
|
@ -68,7 +68,7 @@ jobs:
|
||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.23.2
|
go-version: 1.23.3
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
run: |
|
run: |
|
||||||
|
|
|
@ -43,7 +43,7 @@ linters:
|
||||||
run:
|
run:
|
||||||
# prevent golangci-lint from deducting the go version to lint for through go.mod,
|
# prevent golangci-lint from deducting the go version to lint for through go.mod,
|
||||||
# which causes it to fallback to go1.17 semantics.
|
# which causes it to fallback to go1.17 semantics.
|
||||||
go: "1.23.2"
|
go: "1.23.3"
|
||||||
timeout: 5m
|
timeout: 5m
|
||||||
|
|
||||||
linters-settings:
|
linters-settings:
|
||||||
|
|
|
@ -4,12 +4,12 @@ ARG BASE_VARIANT=alpine
|
||||||
ARG ALPINE_VERSION=3.20
|
ARG ALPINE_VERSION=3.20
|
||||||
ARG BASE_DEBIAN_DISTRO=bookworm
|
ARG BASE_DEBIAN_DISTRO=bookworm
|
||||||
|
|
||||||
ARG GO_VERSION=1.23.2
|
ARG GO_VERSION=1.23.3
|
||||||
ARG XX_VERSION=1.5.0
|
ARG XX_VERSION=1.5.0
|
||||||
ARG GOVERSIONINFO_VERSION=v1.3.0
|
ARG GOVERSIONINFO_VERSION=v1.3.0
|
||||||
ARG GOTESTSUM_VERSION=v1.10.0
|
ARG GOTESTSUM_VERSION=v1.10.0
|
||||||
ARG BUILDX_VERSION=0.17.1
|
ARG BUILDX_VERSION=0.18.0
|
||||||
ARG COMPOSE_VERSION=v2.29.7
|
ARG COMPOSE_VERSION=v2.30.3
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package manager
|
package manager
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package config
|
package config
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
|
//go:build go1.22
|
||||||
|
// +build go1.22
|
||||||
|
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -44,6 +48,65 @@ var allLinuxCapabilities = sync.OnceValue(func() []string {
|
||||||
return out
|
return out
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// logDriverOptions provides the options for each built-in logging driver.
|
||||||
|
var logDriverOptions = map[string][]string{
|
||||||
|
"awslogs": {
|
||||||
|
"max-buffer-size", "mode", "awslogs-create-group", "awslogs-credentials-endpoint", "awslogs-datetime-format",
|
||||||
|
"awslogs-group", "awslogs-multiline-pattern", "awslogs-region", "awslogs-stream", "tag",
|
||||||
|
},
|
||||||
|
"fluentd": {
|
||||||
|
"max-buffer-size", "mode", "env", "env-regex", "labels", "fluentd-address", "fluentd-async",
|
||||||
|
"fluentd-buffer-limit", "fluentd-request-ack", "fluentd-retry-wait", "fluentd-max-retries",
|
||||||
|
"fluentd-sub-second-precision", "tag",
|
||||||
|
},
|
||||||
|
"gcplogs": {
|
||||||
|
"max-buffer-size", "mode", "env", "env-regex", "labels", "gcp-log-cmd", "gcp-meta-id", "gcp-meta-name",
|
||||||
|
"gcp-meta-zone", "gcp-project",
|
||||||
|
},
|
||||||
|
"gelf": {
|
||||||
|
"max-buffer-size", "mode", "env", "env-regex", "labels", "gelf-address", "gelf-compression-level",
|
||||||
|
"gelf-compression-type", "gelf-tcp-max-reconnect", "gelf-tcp-reconnect-delay", "tag",
|
||||||
|
},
|
||||||
|
"journald": {"max-buffer-size", "mode", "env", "env-regex", "labels", "tag"},
|
||||||
|
"json-file": {"max-buffer-size", "mode", "env", "env-regex", "labels", "compress", "max-file", "max-size"},
|
||||||
|
"local": {"max-buffer-size", "mode", "compress", "max-file", "max-size"},
|
||||||
|
"none": {},
|
||||||
|
"splunk": {
|
||||||
|
"max-buffer-size", "mode", "env", "env-regex", "labels", "splunk-caname", "splunk-capath", "splunk-format",
|
||||||
|
"splunk-gzip", "splunk-gzip-level", "splunk-index", "splunk-insecureskipverify", "splunk-source",
|
||||||
|
"splunk-sourcetype", "splunk-token", "splunk-url", "splunk-verify-connection", "tag",
|
||||||
|
},
|
||||||
|
"syslog": {
|
||||||
|
"max-buffer-size", "mode", "env", "env-regex", "labels", "syslog-address", "syslog-facility", "syslog-format",
|
||||||
|
"syslog-tls-ca-cert", "syslog-tls-cert", "syslog-tls-key", "syslog-tls-skip-verify", "tag",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// builtInLogDrivers provides a list of the built-in logging drivers.
|
||||||
|
var builtInLogDrivers = sync.OnceValue(func() []string {
|
||||||
|
drivers := make([]string, 0, len(logDriverOptions))
|
||||||
|
for driver := range logDriverOptions {
|
||||||
|
drivers = append(drivers, driver)
|
||||||
|
}
|
||||||
|
return drivers
|
||||||
|
})
|
||||||
|
|
||||||
|
// allLogDriverOptions provides all options of the built-in logging drivers.
|
||||||
|
// The list does not contain duplicates.
|
||||||
|
var allLogDriverOptions = sync.OnceValue(func() []string {
|
||||||
|
var result []string
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for driver := range logDriverOptions {
|
||||||
|
for _, opt := range logDriverOptions[driver] {
|
||||||
|
if !seen[opt] {
|
||||||
|
seen[opt] = true
|
||||||
|
result = append(result, opt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
|
||||||
// restartPolicies is a list of all valid restart-policies..
|
// restartPolicies is a list of all valid restart-policies..
|
||||||
//
|
//
|
||||||
// TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true")
|
// TODO(thaJeztah): add descriptions, and enable descriptions for our completion scripts (cobra.CompletionOptions.DisableDescriptions is currently set to "true")
|
||||||
|
@ -54,6 +117,207 @@ var restartPolicies = []string{
|
||||||
string(container.RestartPolicyUnlessStopped),
|
string(container.RestartPolicyUnlessStopped),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addCompletions adds the completions that `run` and `create` have in common.
|
||||||
|
func addCompletions(cmd *cobra.Command, dockerCLI completion.APIClientProvider) {
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("attach", completion.FromList("stderr", "stdin", "stdout"))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("cgroupns", completeCgroupns())
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("ipc", completeIpc(dockerCLI))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("link", completeLink(dockerCLI))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("log-driver", completeLogDriver(dockerCLI))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("log-opt", completeLogOpt)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCLI))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("pid", completePid(dockerCLI))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("security-opt", completeSecurityOpt)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("storage-opt", completeStorageOpt)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("ulimit", completeUlimit)
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("userns", completion.FromList("host"))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("uts", completion.FromList("host"))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("volume-driver", completeVolumeDriver(dockerCLI))
|
||||||
|
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCLI, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
// completeCgroupns implements shell completion for the `--cgroupns` option of `run` and `create`.
|
||||||
|
func completeCgroupns() completion.ValidArgsFn {
|
||||||
|
return completion.FromList(string(container.CgroupnsModeHost), string(container.CgroupnsModePrivate))
|
||||||
|
}
|
||||||
|
|
||||||
|
// completeDetachKeys implements shell completion for the `--detach-keys` option of `run` and `create`.
|
||||||
|
func completeDetachKeys(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return []string{"ctrl-"}, cobra.ShellCompDirectiveNoSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
// completeIpc implements shell completion for the `--ipc` option of `run` and `create`.
|
||||||
|
// The completion is partly composite.
|
||||||
|
func completeIpc(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
if len(toComplete) > 0 && strings.HasPrefix("container", toComplete) { //nolint:gocritic // not swapped, matches partly typed "container"
|
||||||
|
return []string{"container:"}, cobra.ShellCompDirectiveNoSpace
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(toComplete, "container:") {
|
||||||
|
names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete)
|
||||||
|
return prefixWith("container:", names), cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
return []string{
|
||||||
|
string(container.IPCModeContainer + ":"),
|
||||||
|
string(container.IPCModeHost),
|
||||||
|
string(container.IPCModeNone),
|
||||||
|
string(container.IPCModePrivate),
|
||||||
|
string(container.IPCModeShareable),
|
||||||
|
}, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// completeLink implements shell completion for the `--link` option of `run` and `create`.
|
||||||
|
func completeLink(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return postfixWith(":", containerNames(dockerCLI, cmd, args, toComplete)), cobra.ShellCompDirectiveNoSpace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// completeLogDriver implements shell completion for the `--log-driver` option of `run` and `create`.
|
||||||
|
// The log drivers are collected from a call to the Info endpoint with a fallback to a hard-coded list
|
||||||
|
// of the build-in log drivers.
|
||||||
|
func completeLogDriver(dockerCLI completion.APIClientProvider) completion.ValidArgsFn {
|
||||||
|
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
info, err := dockerCLI.Client().Info(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
return builtInLogDrivers(), cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
drivers := info.Plugins.Log
|
||||||
|
return drivers, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// completeLogOpt implements shell completion for the `--log-opt` option of `run` and `create`.
|
||||||
|
// If the user supplied a log-driver, only options for that driver are returned.
|
||||||
|
func completeLogOpt(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
driver, _ := cmd.Flags().GetString("log-driver")
|
||||||
|
if options, exists := logDriverOptions[driver]; exists {
|
||||||
|
return postfixWith("=", options), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
return postfixWith("=", allLogDriverOptions()), cobra.ShellCompDirectiveNoSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
// completePid implements shell completion for the `--pid` option of `run` and `create`.
|
||||||
|
func completePid(dockerCLI completion.APIClientProvider) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
if len(toComplete) > 0 && strings.HasPrefix("container", toComplete) { //nolint:gocritic // not swapped, matches partly typed "container"
|
||||||
|
return []string{"container:"}, cobra.ShellCompDirectiveNoSpace
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(toComplete, "container:") {
|
||||||
|
names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete)
|
||||||
|
return prefixWith("container:", names), cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
return []string{"container:", "host"}, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// completeSecurityOpt implements shell completion for the `--security-opt` option of `run` and `create`.
|
||||||
|
// The completion is partly composite.
|
||||||
|
func completeSecurityOpt(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
if len(toComplete) > 0 && strings.HasPrefix("apparmor=", toComplete) { //nolint:gocritic // not swapped, matches partly typed "apparmor="
|
||||||
|
return []string{"apparmor="}, cobra.ShellCompDirectiveNoSpace
|
||||||
|
}
|
||||||
|
if len(toComplete) > 0 && strings.HasPrefix("label", toComplete) { //nolint:gocritic // not swapped, matches partly typed "label"
|
||||||
|
return []string{"label="}, cobra.ShellCompDirectiveNoSpace
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(toComplete, "label=") {
|
||||||
|
if strings.HasPrefix(toComplete, "label=d") {
|
||||||
|
return []string{"label=disable"}, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
labels := []string{"disable", "level:", "role:", "type:", "user:"}
|
||||||
|
return prefixWith("label=", labels), cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
// length must be > 1 here so that completion of "s" falls through.
|
||||||
|
if len(toComplete) > 1 && strings.HasPrefix("seccomp", toComplete) { //nolint:gocritic // not swapped, matches partly typed "seccomp"
|
||||||
|
return []string{"seccomp="}, cobra.ShellCompDirectiveNoSpace
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(toComplete, "seccomp=") {
|
||||||
|
return []string{"seccomp=unconfined"}, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
return []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"}, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
|
||||||
|
// completeStorageOpt implements shell completion for the `--storage-opt` option of `run` and `create`.
|
||||||
|
func completeStorageOpt(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
return []string{"size="}, cobra.ShellCompDirectiveNoSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
// completeUlimit implements shell completion for the `--ulimit` option of `run` and `create`.
|
||||||
|
func completeUlimit(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
limits := []string{
|
||||||
|
"as",
|
||||||
|
"chroot",
|
||||||
|
"core",
|
||||||
|
"cpu",
|
||||||
|
"data",
|
||||||
|
"fsize",
|
||||||
|
"locks",
|
||||||
|
"maxlogins",
|
||||||
|
"maxsyslogins",
|
||||||
|
"memlock",
|
||||||
|
"msgqueue",
|
||||||
|
"nice",
|
||||||
|
"nofile",
|
||||||
|
"nproc",
|
||||||
|
"priority",
|
||||||
|
"rss",
|
||||||
|
"rtprio",
|
||||||
|
"sigpending",
|
||||||
|
"stack",
|
||||||
|
}
|
||||||
|
return postfixWith("=", limits), cobra.ShellCompDirectiveNoSpace
|
||||||
|
}
|
||||||
|
|
||||||
|
// completeVolumeDriver contacts the API to get the built-in and installed volume drivers.
|
||||||
|
func completeVolumeDriver(dockerCLI completion.APIClientProvider) completion.ValidArgsFn {
|
||||||
|
return func(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
info, err := dockerCLI.Client().Info(cmd.Context())
|
||||||
|
if err != nil {
|
||||||
|
// fallback: the built-in drivers
|
||||||
|
return []string{"local"}, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
drivers := info.Plugins.Volume
|
||||||
|
return drivers, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// containerNames contacts the API to get names and optionally IDs of containers.
|
||||||
|
// In case of an error, an empty list is returned.
|
||||||
|
func containerNames(dockerCLI completion.APIClientProvider, cmd *cobra.Command, args []string, toComplete string) []string {
|
||||||
|
names, _ := completion.ContainerNames(dockerCLI, true)(cmd, args, toComplete)
|
||||||
|
if names == nil {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// prefixWith prefixes every element in the slice with the given prefix.
|
||||||
|
func prefixWith(prefix string, values []string) []string {
|
||||||
|
result := make([]string, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
result[i] = prefix + v
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// postfixWith appends postfix to every element in the slice.
|
||||||
|
func postfixWith(postfix string, values []string) []string {
|
||||||
|
result := make([]string, len(values))
|
||||||
|
for i, v := range values {
|
||||||
|
result[i] = v + postfix
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func completeLinuxCapabilityNames(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
|
func completeLinuxCapabilityNames(cmd *cobra.Command, args []string, toComplete string) (names []string, _ cobra.ShellCompDirective) {
|
||||||
return completion.FromList(allLinuxCapabilities()...)(cmd, args, toComplete)
|
return completion.FromList(allLinuxCapabilities()...)(cmd, args, toComplete)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/internal/test"
|
||||||
|
"github.com/docker/cli/internal/test/builders"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/moby/sys/signal"
|
"github.com/moby/sys/signal"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
|
@ -21,6 +24,48 @@ func TestCompleteLinuxCapabilityNames(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompletePid(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
containerListFunc func(container.ListOptions) ([]container.Summary, error)
|
||||||
|
toComplete string
|
||||||
|
expectedCompletions []string
|
||||||
|
expectedDirective cobra.ShellCompDirective
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
toComplete: "",
|
||||||
|
expectedCompletions: []string{"container:", "host"},
|
||||||
|
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toComplete: "c",
|
||||||
|
expectedCompletions: []string{"container:"},
|
||||||
|
expectedDirective: cobra.ShellCompDirectiveNoSpace,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
containerListFunc: func(container.ListOptions) ([]container.Summary, error) {
|
||||||
|
return []container.Summary{
|
||||||
|
*builders.Container("c1"),
|
||||||
|
*builders.Container("c2"),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
toComplete: "container:",
|
||||||
|
expectedCompletions: []string{"container:c1", "container:c2"},
|
||||||
|
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.toComplete, func(t *testing.T) {
|
||||||
|
cli := test.NewFakeCli(&fakeClient{
|
||||||
|
containerListFunc: tc.containerListFunc,
|
||||||
|
})
|
||||||
|
completions, directive := completePid(cli)(NewRunCommand(cli), nil, tc.toComplete)
|
||||||
|
assert.Check(t, is.DeepEqual(completions, tc.expectedCompletions))
|
||||||
|
assert.Check(t, is.Equal(directive, tc.expectedDirective))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCompleteRestartPolicies(t *testing.T) {
|
func TestCompleteRestartPolicies(t *testing.T) {
|
||||||
values, directives := completeRestartPolicies(nil, nil, "")
|
values, directives := completeRestartPolicies(nil, nil, "")
|
||||||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
||||||
|
@ -28,6 +73,59 @@ func TestCompleteRestartPolicies(t *testing.T) {
|
||||||
assert.Check(t, is.DeepEqual(values, expected))
|
assert.Check(t, is.DeepEqual(values, expected))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompleteSecurityOpt(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
toComplete string
|
||||||
|
expectedCompletions []string
|
||||||
|
expectedDirective cobra.ShellCompDirective
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
toComplete: "",
|
||||||
|
expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"},
|
||||||
|
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toComplete: "apparmor=",
|
||||||
|
expectedCompletions: []string{"apparmor="},
|
||||||
|
expectedDirective: cobra.ShellCompDirectiveNoSpace,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toComplete: "label=",
|
||||||
|
expectedCompletions: []string{"label=disable", "label=level:", "label=role:", "label=type:", "label=user:"},
|
||||||
|
expectedDirective: cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toComplete: "s",
|
||||||
|
// We do not filter matching completions but delegate this task to the shell script.
|
||||||
|
expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"},
|
||||||
|
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toComplete: "se",
|
||||||
|
expectedCompletions: []string{"seccomp="},
|
||||||
|
expectedDirective: cobra.ShellCompDirectiveNoSpace,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toComplete: "seccomp=",
|
||||||
|
expectedCompletions: []string{"seccomp=unconfined"},
|
||||||
|
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
toComplete: "sy",
|
||||||
|
expectedCompletions: []string{"apparmor=", "label=", "no-new-privileges", "seccomp=", "systempaths=unconfined"},
|
||||||
|
expectedDirective: cobra.ShellCompDirectiveNoFileComp,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.toComplete, func(t *testing.T) {
|
||||||
|
completions, directive := completeSecurityOpt(nil, nil, tc.toComplete)
|
||||||
|
assert.Check(t, is.DeepEqual(completions, tc.expectedCompletions))
|
||||||
|
assert.Check(t, is.Equal(directive, tc.expectedDirective))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCompleteSignals(t *testing.T) {
|
func TestCompleteSignals(t *testing.T) {
|
||||||
values, directives := completeSignals(nil, nil, "")
|
values, directives := completeSignals(nil, nil, "")
|
||||||
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
assert.Check(t, is.Equal(directives&cobra.ShellCompDirectiveNoFileComp, cobra.ShellCompDirectiveNoFileComp), "Should not perform file completion")
|
||||||
|
|
|
@ -78,16 +78,15 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
||||||
copts = addFlags(flags)
|
copts = addFlags(flags)
|
||||||
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames)
|
addCompletions(cmd, dockerCli)
|
||||||
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
|
flags.VisitAll(func(flag *pflag.Flag) {
|
||||||
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
|
// Set a default completion function if none was set. We don't look
|
||||||
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli))
|
// up if it does already have one set, because Cobra does this for
|
||||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
// us, and returns an error (which we ignore for this reason).
|
||||||
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
|
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||||
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
|
})
|
||||||
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package container
|
package container
|
||||||
|
|
||||||
|
|
|
@ -69,16 +69,16 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled())
|
||||||
copts = addFlags(flags)
|
copts = addFlags(flags)
|
||||||
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("cap-add", completeLinuxCapabilityNames)
|
_ = cmd.RegisterFlagCompletionFunc("detach-keys", completeDetachKeys)
|
||||||
_ = cmd.RegisterFlagCompletionFunc("cap-drop", completeLinuxCapabilityNames)
|
addCompletions(cmd, dockerCli)
|
||||||
_ = cmd.RegisterFlagCompletionFunc("env", completion.EnvVarNames)
|
|
||||||
_ = cmd.RegisterFlagCompletionFunc("env-file", completion.FileNames)
|
flags.VisitAll(func(flag *pflag.Flag) {
|
||||||
_ = cmd.RegisterFlagCompletionFunc("network", completion.NetworkNames(dockerCli))
|
// Set a default completion function if none was set. We don't look
|
||||||
_ = cmd.RegisterFlagCompletionFunc("platform", completion.Platforms)
|
// up if it does already have one set, because Cobra does this for
|
||||||
_ = cmd.RegisterFlagCompletionFunc("pull", completion.FromList(PullImageAlways, PullImageMissing, PullImageNever))
|
// us, and returns an error (which we ignore for this reason).
|
||||||
_ = cmd.RegisterFlagCompletionFunc("restart", completeRestartPolicies)
|
_ = cmd.RegisterFlagCompletionFunc(flag.Name, completion.NoComplete)
|
||||||
_ = cmd.RegisterFlagCompletionFunc("stop-signal", completeSignals)
|
})
|
||||||
_ = cmd.RegisterFlagCompletionFunc("volumes-from", completion.ContainerNames(dockerCli, true))
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package context
|
package context
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package context
|
package context
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package context
|
package context
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package context
|
package context
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package formatter
|
package formatter
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package idresolver
|
package idresolver
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package image
|
package image
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package image
|
package image
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package inspect
|
package inspect
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ func NewManifestCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
newAnnotateCommand(dockerCli),
|
newAnnotateCommand(dockerCli),
|
||||||
newPushListCommand(dockerCli),
|
newPushListCommand(dockerCli),
|
||||||
newRmManifestListCommand(dockerCli),
|
newRmManifestListCommand(dockerCli),
|
||||||
|
newListCommand(dockerCli),
|
||||||
)
|
)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package manifest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/cli/manifest/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultManifestListQuietFormat = "{{.Name}}"
|
||||||
|
defaultManifestListTableFormat = "table {{.Repository}}\t{{.Tag}}\t{{.Platforms}}"
|
||||||
|
|
||||||
|
repositoryHeader = "REPOSITORY"
|
||||||
|
tagHeader = "TAG"
|
||||||
|
platformsHeader = "PLATFORMS"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewFormat returns a Format for rendering using a manifest list Context
|
||||||
|
func NewFormat(source string, quiet bool) formatter.Format {
|
||||||
|
switch source {
|
||||||
|
case formatter.TableFormatKey:
|
||||||
|
if quiet {
|
||||||
|
return defaultManifestListQuietFormat
|
||||||
|
}
|
||||||
|
return defaultManifestListTableFormat
|
||||||
|
case formatter.RawFormatKey:
|
||||||
|
if quiet {
|
||||||
|
return `name: {{.Name}}`
|
||||||
|
}
|
||||||
|
return `repo: {{.Repository}}\ntag: {{.Tag}}\n`
|
||||||
|
}
|
||||||
|
return formatter.Format(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatWrite writes formatted manifestLists using the Context
|
||||||
|
func FormatWrite(ctx formatter.Context, manifestLists []reference.Reference, manifests map[string][]types.ImageManifest) error {
|
||||||
|
render := func(format func(subContext formatter.SubContext) error) error {
|
||||||
|
for _, manifestList := range manifestLists {
|
||||||
|
if n, ok := manifestList.(reference.Named); ok {
|
||||||
|
if nt, ok := n.(reference.NamedTagged); ok {
|
||||||
|
if err := format(&manifestListContext{
|
||||||
|
name: reference.FamiliarString(manifestList),
|
||||||
|
repo: reference.FamiliarName(nt),
|
||||||
|
tag: nt.Tag(),
|
||||||
|
imageManifests: manifests[manifestList.String()],
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ctx.Write(newManifestListContext(), render)
|
||||||
|
}
|
||||||
|
|
||||||
|
type manifestListContext struct {
|
||||||
|
formatter.HeaderContext
|
||||||
|
name string
|
||||||
|
repo string
|
||||||
|
tag string
|
||||||
|
imageManifests []types.ImageManifest
|
||||||
|
}
|
||||||
|
|
||||||
|
func newManifestListContext() *manifestListContext {
|
||||||
|
manifestListCtx := manifestListContext{}
|
||||||
|
manifestListCtx.Header = formatter.SubHeaderContext{
|
||||||
|
"Name": formatter.NameHeader,
|
||||||
|
"Repository": repositoryHeader,
|
||||||
|
"Tag": tagHeader,
|
||||||
|
"Platforms": platformsHeader,
|
||||||
|
}
|
||||||
|
return &manifestListCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *manifestListContext) MarshalJSON() ([]byte, error) {
|
||||||
|
return formatter.MarshalJSON(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *manifestListContext) Name() string {
|
||||||
|
return c.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *manifestListContext) Repository() string {
|
||||||
|
return c.repo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *manifestListContext) Tag() string {
|
||||||
|
return c.tag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *manifestListContext) Platforms() string {
|
||||||
|
platforms := []string{}
|
||||||
|
for _, manifest := range c.imageManifests {
|
||||||
|
os := manifest.Descriptor.Platform.OS
|
||||||
|
arch := manifest.Descriptor.Platform.Architecture
|
||||||
|
platforms = append(platforms, fmt.Sprintf("%s/%s", os, arch))
|
||||||
|
}
|
||||||
|
return strings.Join(platforms, ", ")
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package manifest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/distribution/reference"
|
||||||
|
"github.com/docker/cli/cli"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
flagsHelper "github.com/docker/cli/cli/flags"
|
||||||
|
"github.com/docker/cli/cli/manifest/types"
|
||||||
|
"github.com/fvbommel/sortorder"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
type listOptions struct {
|
||||||
|
quiet bool
|
||||||
|
format string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
|
var options listOptions
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "ls [OPTIONS]",
|
||||||
|
Aliases: []string{"list"},
|
||||||
|
Short: "List local manifest lists",
|
||||||
|
Args: cli.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runList(dockerCli, options)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show manifest list NAMEs")
|
||||||
|
flags.StringVar(&options.format, "format", "", flagsHelper.FormatHelp)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func runList(dockerCli command.Cli, options listOptions) error {
|
||||||
|
manifestStore := dockerCli.ManifestStore()
|
||||||
|
|
||||||
|
var manifestLists []reference.Reference
|
||||||
|
|
||||||
|
manifestLists, err := manifestStore.List()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
manifests := map[string][]types.ImageManifest{}
|
||||||
|
for _, manifestList := range manifestLists {
|
||||||
|
if imageManifests, err := manifestStore.GetList(manifestList); err == nil {
|
||||||
|
manifests[manifestList.String()] = imageManifests
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
format := options.format
|
||||||
|
if len(format) == 0 {
|
||||||
|
if len(dockerCli.ConfigFile().ManifestListsFormat) > 0 && !options.quiet {
|
||||||
|
format = dockerCli.ConfigFile().ManifestListsFormat
|
||||||
|
} else {
|
||||||
|
format = formatter.TableFormatKey
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestListsCtx := formatter.Context{
|
||||||
|
Output: dockerCli.Out(),
|
||||||
|
Format: NewFormat(format, options.quiet),
|
||||||
|
}
|
||||||
|
sort.Slice(manifestLists, func(i, j int) bool {
|
||||||
|
return sortorder.NaturalLess(manifestLists[i].String(), manifestLists[j].String())
|
||||||
|
})
|
||||||
|
return FormatWrite(manifestListsCtx, manifestLists, manifests)
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
package manifest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/manifest/store"
|
||||||
|
"github.com/docker/cli/internal/test"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
|
||||||
|
"gotest.tools/v3/assert"
|
||||||
|
"gotest.tools/v3/golden"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestListErrors(t *testing.T) {
|
||||||
|
manifestStore := store.NewStore(t.TempDir())
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
args []string
|
||||||
|
flags map[string]string
|
||||||
|
expectedError string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "too many arguments",
|
||||||
|
args: []string{"foo"},
|
||||||
|
expectedError: "accepts no arguments",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "invalid format",
|
||||||
|
args: []string{},
|
||||||
|
flags: map[string]string{
|
||||||
|
"format": "{{invalid format}}",
|
||||||
|
},
|
||||||
|
expectedError: "template parsing error",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
cli := test.NewFakeCli(nil)
|
||||||
|
cli.SetManifestStore(manifestStore)
|
||||||
|
cmd := newListCommand(cli)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
for key, value := range tc.flags {
|
||||||
|
cmd.Flags().Set(key, value)
|
||||||
|
}
|
||||||
|
cmd.SetOut(io.Discard)
|
||||||
|
assert.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestList(t *testing.T) {
|
||||||
|
manifestStore := store.NewStore(t.TempDir())
|
||||||
|
|
||||||
|
list1 := ref(t, "first:1")
|
||||||
|
namedRef := ref(t, "alpine:3.0")
|
||||||
|
err := manifestStore.Save(list1, namedRef, fullImageManifest(t, namedRef))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
namedRef = ref(t, "alpine:3.1")
|
||||||
|
imageManifest := fullImageManifest(t, namedRef)
|
||||||
|
imageManifest.Descriptor.Platform.OS = "linux"
|
||||||
|
imageManifest.Descriptor.Platform.Architecture = "arm64"
|
||||||
|
err = manifestStore.Save(list1, namedRef, imageManifest)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
list2 := ref(t, "second:2")
|
||||||
|
namedRef = ref(t, "alpine:3.2")
|
||||||
|
err = manifestStore.Save(list2, namedRef, fullImageManifest(t, namedRef))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
description string
|
||||||
|
args []string
|
||||||
|
flags map[string]string
|
||||||
|
golden string
|
||||||
|
listFunc func(filter filters.Args) (types.PluginsListResponse, error)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
description: "list with no additional flags",
|
||||||
|
args: []string{},
|
||||||
|
golden: "manifest-list.golden",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: "list with quiet option",
|
||||||
|
args: []string{},
|
||||||
|
flags: map[string]string{
|
||||||
|
"quiet": "true",
|
||||||
|
},
|
||||||
|
golden: "manifest-list-with-quiet-option.golden",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
cli := test.NewFakeCli(nil)
|
||||||
|
cli.SetManifestStore(manifestStore)
|
||||||
|
cmd := newListCommand(cli)
|
||||||
|
cmd.SetArgs(tc.args)
|
||||||
|
for key, value := range tc.flags {
|
||||||
|
cmd.Flags().Set(key, value)
|
||||||
|
}
|
||||||
|
assert.NilError(t, cmd.Execute())
|
||||||
|
golden.Assert(t, cli.OutBuffer().String(), tc.golden)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
example.com/first:1
|
||||||
|
example.com/second:2
|
|
@ -0,0 +1,3 @@
|
||||||
|
REPOSITORY TAG PLATFORMS
|
||||||
|
example.com/first 1 linux/amd64, linux/arm64
|
||||||
|
example.com/second 2 linux/amd64
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package network
|
package network
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package network
|
package network
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package node
|
package node
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package node
|
package node
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package plugin
|
package plugin
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package secret
|
package secret
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package service
|
package service
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package service
|
package service
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package service
|
package service
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package service
|
package service
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package loader
|
package loader
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package system
|
package system
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package system
|
package system
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(jsternberg): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package trust
|
package trust
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package command
|
package command
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package volume
|
package volume
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package interpolation
|
package interpolation
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package interpolation
|
package interpolation
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package loader
|
package loader
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package loader
|
package loader
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package loader
|
package loader
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package loader
|
package loader
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package loader
|
package loader
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package loader
|
package loader
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package schema
|
package schema
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package schema
|
package schema
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package template
|
package template
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ type ConfigFile struct {
|
||||||
PluginsFormat string `json:"pluginsFormat,omitempty"`
|
PluginsFormat string `json:"pluginsFormat,omitempty"`
|
||||||
VolumesFormat string `json:"volumesFormat,omitempty"`
|
VolumesFormat string `json:"volumesFormat,omitempty"`
|
||||||
StatsFormat string `json:"statsFormat,omitempty"`
|
StatsFormat string `json:"statsFormat,omitempty"`
|
||||||
|
ManifestListsFormat string `json:"manifestListsFormat,omitempty"`
|
||||||
DetachKeys string `json:"detachKeys,omitempty"`
|
DetachKeys string `json:"detachKeys,omitempty"`
|
||||||
CredentialsStore string `json:"credsStore,omitempty"`
|
CredentialsStore string `json:"credsStore,omitempty"`
|
||||||
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
|
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package store
|
package store
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package store
|
package store
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package store
|
package store
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package store
|
package store
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package store
|
package store
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package store
|
package store
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package api
|
package api
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ type Store interface {
|
||||||
Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error)
|
Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error)
|
||||||
GetList(listRef reference.Reference) ([]types.ImageManifest, error)
|
GetList(listRef reference.Reference) ([]types.ImageManifest, error)
|
||||||
Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error
|
Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error
|
||||||
|
List() ([]reference.Reference, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fsStore manages manifest files stored on the local filesystem
|
// fsStore manages manifest files stored on the local filesystem
|
||||||
|
@ -85,6 +86,26 @@ func (s *fsStore) getFromFilename(ref reference.Reference, filename string) (typ
|
||||||
return manifestInfo.ImageManifest, nil
|
return manifestInfo.ImageManifest, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List returns the local manifest lists for a transaction
|
||||||
|
func (s *fsStore) List() ([]reference.Reference, error) {
|
||||||
|
fileInfos, err := os.ReadDir(s.root)
|
||||||
|
switch {
|
||||||
|
case os.IsNotExist(err):
|
||||||
|
return nil, nil
|
||||||
|
case err != nil:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
listRefs := make([]reference.Reference, 0, len(fileInfos))
|
||||||
|
for _, info := range fileInfos {
|
||||||
|
refString := filenameToRefString(info.Name())
|
||||||
|
if listRef, err := reference.Parse(refString); err == nil {
|
||||||
|
listRefs = append(listRefs, listRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return listRefs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetList returns all the local manifests for a transaction
|
// GetList returns all the local manifests for a transaction
|
||||||
func (s *fsStore) GetList(listRef reference.Reference) ([]types.ImageManifest, error) {
|
func (s *fsStore) GetList(listRef reference.Reference) ([]types.ImageManifest, error) {
|
||||||
filenames, err := s.listManifests(listRef.String())
|
filenames, err := s.listManifests(listRef.String())
|
||||||
|
@ -148,8 +169,13 @@ func manifestToFilename(root, manifestList, manifest string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeFilesafeName(ref string) string {
|
func makeFilesafeName(ref string) string {
|
||||||
fileName := strings.ReplaceAll(ref, ":", "-")
|
fileName := strings.ReplaceAll(ref, ":", "%")
|
||||||
return strings.ReplaceAll(fileName, "/", "_")
|
return strings.ReplaceAll(fileName, "/", "#")
|
||||||
|
}
|
||||||
|
|
||||||
|
func filenameToRefString(filename string) string {
|
||||||
|
refString := strings.ReplaceAll(filename, "%", ":")
|
||||||
|
return strings.ReplaceAll(refString, "#", "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
type notFoundError struct {
|
type notFoundError struct {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
|
|
|
@ -4139,10 +4139,15 @@ _docker_manifest() {
|
||||||
annotate
|
annotate
|
||||||
create
|
create
|
||||||
inspect
|
inspect
|
||||||
|
ls
|
||||||
push
|
push
|
||||||
rm
|
rm
|
||||||
"
|
"
|
||||||
__docker_subcommands "$subcommands" && return
|
|
||||||
|
local aliases="
|
||||||
|
list
|
||||||
|
"
|
||||||
|
__docker_subcommands "$subcommands $aliases" && return
|
||||||
|
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
|
@ -4250,6 +4255,24 @@ _docker_manifest_rm() {
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_docker_manifest_list() {
|
||||||
|
_docker_manifest_ls
|
||||||
|
}
|
||||||
|
|
||||||
|
_docker_manifest_ls() {
|
||||||
|
case "$prev" in
|
||||||
|
--format)
|
||||||
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
case "$cur" in
|
||||||
|
-*)
|
||||||
|
COMPREPLY=( $( compgen -W "--format --help --quiet -q" -- "$cur" ) )
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
_docker_node() {
|
_docker_node() {
|
||||||
local subcommands="
|
local subcommands="
|
||||||
demote
|
demote
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
variable "GO_VERSION" {
|
variable "GO_VERSION" {
|
||||||
default = "1.23.2"
|
default = "1.23.3"
|
||||||
}
|
}
|
||||||
variable "VERSION" {
|
variable "VERSION" {
|
||||||
default = ""
|
default = ""
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.23.2
|
ARG GO_VERSION=1.23.3
|
||||||
ARG ALPINE_VERSION=3.20
|
ARG ALPINE_VERSION=3.20
|
||||||
|
|
||||||
ARG BUILDX_VERSION=0.17.1
|
ARG BUILDX_VERSION=0.17.1
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.23.2
|
ARG GO_VERSION=1.23.3
|
||||||
ARG ALPINE_VERSION=3.20
|
ARG ALPINE_VERSION=3.20
|
||||||
ARG GOLANGCI_LINT_VERSION=v1.61.0
|
ARG GOLANGCI_LINT_VERSION=v1.61.0
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.23.2
|
ARG GO_VERSION=1.23.3
|
||||||
ARG ALPINE_VERSION=3.20
|
ARG ALPINE_VERSION=3.20
|
||||||
ARG MODOUTDATED_VERSION=v0.8.0
|
ARG MODOUTDATED_VERSION=v0.8.0
|
||||||
|
|
||||||
|
|
|
@ -1283,7 +1283,7 @@ connect to services running on the host machine.
|
||||||
|
|
||||||
It's conventional to use `host.docker.internal` as the hostname referring to
|
It's conventional to use `host.docker.internal` as the hostname referring to
|
||||||
`host-gateway`. Docker Desktop automatically resolves this hostname, see
|
`host-gateway`. Docker Desktop automatically resolves this hostname, see
|
||||||
[Explore networking features](https://docs.docker.com/desktop/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host).
|
[Explore networking features](https://docs.docker.com/desktop/features/networking/#i-want-to-connect-from-a-container-to-a-service-on-the-host).
|
||||||
|
|
||||||
The following example shows how the special `host-gateway` value works. The
|
The following example shows how the special `host-gateway` value works. The
|
||||||
example runs an HTTP server that serves a file from host to container over the
|
example runs an HTTP server that serves a file from host to container over the
|
||||||
|
|
|
@ -21,6 +21,7 @@ For full details on using docker manifest lists, see the registry v2 specificati
|
||||||
| [`annotate`](manifest_annotate.md) | Add additional information to a local image manifest |
|
| [`annotate`](manifest_annotate.md) | Add additional information to a local image manifest |
|
||||||
| [`create`](manifest_create.md) | Create a local manifest list for annotating and pushing to a registry |
|
| [`create`](manifest_create.md) | Create a local manifest list for annotating and pushing to a registry |
|
||||||
| [`inspect`](manifest_inspect.md) | Display an image manifest, or manifest list |
|
| [`inspect`](manifest_inspect.md) | Display an image manifest, or manifest list |
|
||||||
|
| [`ls`](manifest_ls.md) | List local manifest lists |
|
||||||
| [`push`](manifest_push.md) | Push a manifest list to a repository |
|
| [`push`](manifest_push.md) | Push a manifest list to a repository |
|
||||||
| [`rm`](manifest_rm.md) | Delete one or more manifest lists from local storage |
|
| [`rm`](manifest_rm.md) | Delete one or more manifest lists from local storage |
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# docker manifest ls
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
List local manifest lists
|
||||||
|
|
||||||
|
### Aliases
|
||||||
|
|
||||||
|
`docker manifest ls`, `docker manifest list`
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
|:----------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `--format` | `string` | | Format output using a custom template:<br>'table': Print output in table format with column headers (default)<br>'table TEMPLATE': Print output in table format using the given Go template<br>'json': Print in JSON format<br>'TEMPLATE': Print output using the given Go template.<br>Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
|
||||||
|
| `-q`, `--quiet` | | | Only show manifest list NAMEs |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
|
@ -34,11 +34,11 @@ information about available filter options.
|
||||||
$ docker node ps swarm-manager1
|
$ docker node ps swarm-manager1
|
||||||
|
|
||||||
NAME IMAGE NODE DESIRED STATE CURRENT STATE
|
NAME IMAGE NODE DESIRED STATE CURRENT STATE
|
||||||
redis.1.7q92v0nr1hcgts2amcjyqg3pq redis:3.0.6 swarm-manager1 Running Running 5 hours
|
redis.1.7q92v0nr1hcgts2amcjyqg3pq redis:7.4.1 swarm-manager1 Running Running 5 hours
|
||||||
redis.6.b465edgho06e318egmgjbqo4o redis:3.0.6 swarm-manager1 Running Running 29 seconds
|
redis.6.b465edgho06e318egmgjbqo4o redis:7.4.1 swarm-manager1 Running Running 29 seconds
|
||||||
redis.7.bg8c07zzg87di2mufeq51a2qp redis:3.0.6 swarm-manager1 Running Running 5 seconds
|
redis.7.bg8c07zzg87di2mufeq51a2qp redis:7.4.1 swarm-manager1 Running Running 5 seconds
|
||||||
redis.9.dkkual96p4bb3s6b10r7coxxt redis:3.0.6 swarm-manager1 Running Running 5 seconds
|
redis.9.dkkual96p4bb3s6b10r7coxxt redis:7.4.1 swarm-manager1 Running Running 5 seconds
|
||||||
redis.10.0tgctg8h8cech4w0k0gwrmr23 redis:3.0.6 swarm-manager1 Running Running 5 seconds
|
redis.10.0tgctg8h8cech4w0k0gwrmr23 redis:7.4.1 swarm-manager1 Running Running 5 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
### <a name="filter"></a> Filtering (--filter)
|
### <a name="filter"></a> Filtering (--filter)
|
||||||
|
@ -64,11 +64,11 @@ The following filter matches all tasks with a name containing the `redis` string
|
||||||
$ docker node ps -f name=redis swarm-manager1
|
$ docker node ps -f name=redis swarm-manager1
|
||||||
|
|
||||||
NAME IMAGE NODE DESIRED STATE CURRENT STATE
|
NAME IMAGE NODE DESIRED STATE CURRENT STATE
|
||||||
redis.1.7q92v0nr1hcgts2amcjyqg3pq redis:3.0.6 swarm-manager1 Running Running 5 hours
|
redis.1.7q92v0nr1hcgts2amcjyqg3pq redis:7.4.1 swarm-manager1 Running Running 5 hours
|
||||||
redis.6.b465edgho06e318egmgjbqo4o redis:3.0.6 swarm-manager1 Running Running 29 seconds
|
redis.6.b465edgho06e318egmgjbqo4o redis:7.4.1 swarm-manager1 Running Running 29 seconds
|
||||||
redis.7.bg8c07zzg87di2mufeq51a2qp redis:3.0.6 swarm-manager1 Running Running 5 seconds
|
redis.7.bg8c07zzg87di2mufeq51a2qp redis:7.4.1 swarm-manager1 Running Running 5 seconds
|
||||||
redis.9.dkkual96p4bb3s6b10r7coxxt redis:3.0.6 swarm-manager1 Running Running 5 seconds
|
redis.9.dkkual96p4bb3s6b10r7coxxt redis:7.4.1 swarm-manager1 Running Running 5 seconds
|
||||||
redis.10.0tgctg8h8cech4w0k0gwrmr23 redis:3.0.6 swarm-manager1 Running Running 5 seconds
|
redis.10.0tgctg8h8cech4w0k0gwrmr23 redis:7.4.1 swarm-manager1 Running Running 5 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
#### id
|
#### id
|
||||||
|
@ -79,7 +79,7 @@ The `id` filter matches a task's id.
|
||||||
$ docker node ps -f id=bg8c07zzg87di2mufeq51a2qp swarm-manager1
|
$ docker node ps -f id=bg8c07zzg87di2mufeq51a2qp swarm-manager1
|
||||||
|
|
||||||
NAME IMAGE NODE DESIRED STATE CURRENT STATE
|
NAME IMAGE NODE DESIRED STATE CURRENT STATE
|
||||||
redis.7.bg8c07zzg87di2mufeq51a2qp redis:3.0.6 swarm-manager1 Running Running 5 seconds
|
redis.7.bg8c07zzg87di2mufeq51a2qp redis:7.4.1 swarm-manager1 Running Running 5 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
#### label
|
#### label
|
||||||
|
@ -93,8 +93,8 @@ The following filter matches tasks with the `usage` label regardless of its valu
|
||||||
$ docker node ps -f "label=usage"
|
$ docker node ps -f "label=usage"
|
||||||
|
|
||||||
NAME IMAGE NODE DESIRED STATE CURRENT STATE
|
NAME IMAGE NODE DESIRED STATE CURRENT STATE
|
||||||
redis.6.b465edgho06e318egmgjbqo4o redis:3.0.6 swarm-manager1 Running Running 10 minutes
|
redis.6.b465edgho06e318egmgjbqo4o redis:7.4.1 swarm-manager1 Running Running 10 minutes
|
||||||
redis.7.bg8c07zzg87di2mufeq51a2qp redis:3.0.6 swarm-manager1 Running Running 9 minutes
|
redis.7.bg8c07zzg87di2mufeq51a2qp redis:7.4.1 swarm-manager1 Running Running 9 minutes
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -99,19 +99,19 @@ Creates a service as described by the specified parameters.
|
||||||
### Create a service
|
### Create a service
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker service create --name redis redis:3.0.6
|
$ docker service create --name redis redis:7.4.1
|
||||||
|
|
||||||
dmu1ept4cxcfe8k8lhtux3ro3
|
dmu1ept4cxcfe8k8lhtux3ro3
|
||||||
|
|
||||||
$ docker service create --mode global --name redis2 redis:3.0.6
|
$ docker service create --mode global --name redis2 redis:7.4.1
|
||||||
|
|
||||||
a8q9dasaafudfs8q8w32udass
|
a8q9dasaafudfs8q8w32udass
|
||||||
|
|
||||||
$ docker service ls
|
$ docker service ls
|
||||||
|
|
||||||
ID NAME MODE REPLICAS IMAGE
|
ID NAME MODE REPLICAS IMAGE
|
||||||
dmu1ept4cxcf redis replicated 1/1 redis:3.0.6
|
dmu1ept4cxcf redis replicated 1/1 redis:7.4.1
|
||||||
a8q9dasaafud redis2 global 1/1 redis:3.0.6
|
a8q9dasaafud redis2 global 1/1 redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
#### <a name="with-registry-auth"></a> Create a service using an image on a private registry (--with-registry-auth)
|
#### <a name="with-registry-auth"></a> Create a service using an image on a private registry (--with-registry-auth)
|
||||||
|
@ -140,7 +140,7 @@ Use the `--replicas` flag to set the number of replica tasks for a replicated
|
||||||
service. The following command creates a `redis` service with `5` replica tasks:
|
service. The following command creates a `redis` service with `5` replica tasks:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker service create --name redis --replicas=5 redis:3.0.6
|
$ docker service create --name redis --replicas=5 redis:7.4.1
|
||||||
|
|
||||||
4cdgfyky7ozwh3htjfw0d12qv
|
4cdgfyky7ozwh3htjfw0d12qv
|
||||||
```
|
```
|
||||||
|
@ -157,7 +157,7 @@ number of `RUNNING` tasks is `3`:
|
||||||
$ docker service ls
|
$ docker service ls
|
||||||
|
|
||||||
ID NAME MODE REPLICAS IMAGE
|
ID NAME MODE REPLICAS IMAGE
|
||||||
4cdgfyky7ozw redis replicated 3/5 redis:3.0.7
|
4cdgfyky7ozw redis replicated 3/5 redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
Once all the tasks are created and `RUNNING`, the actual number of tasks is
|
Once all the tasks are created and `RUNNING`, the actual number of tasks is
|
||||||
|
@ -167,7 +167,7 @@ equal to the desired number:
|
||||||
$ docker service ls
|
$ docker service ls
|
||||||
|
|
||||||
ID NAME MODE REPLICAS IMAGE
|
ID NAME MODE REPLICAS IMAGE
|
||||||
4cdgfyky7ozw redis replicated 5/5 redis:3.0.7
|
4cdgfyky7ozw redis replicated 5/5 redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
### <a name="secret"></a> Create a service with secrets (--secret)
|
### <a name="secret"></a> Create a service with secrets (--secret)
|
||||||
|
@ -178,7 +178,7 @@ Use the `--secret` flag to give a container access to a
|
||||||
Create a service specifying a secret:
|
Create a service specifying a secret:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker service create --name redis --secret secret.json redis:3.0.6
|
$ docker service create --name redis --secret secret.json redis:7.4.1
|
||||||
|
|
||||||
4cdgfyky7ozwh3htjfw0d12qv
|
4cdgfyky7ozwh3htjfw0d12qv
|
||||||
```
|
```
|
||||||
|
@ -189,7 +189,7 @@ Create a service specifying the secret, target, user/group ID, and mode:
|
||||||
$ docker service create --name redis \
|
$ docker service create --name redis \
|
||||||
--secret source=ssh-key,target=ssh \
|
--secret source=ssh-key,target=ssh \
|
||||||
--secret source=app-key,target=app,uid=1000,gid=1001,mode=0400 \
|
--secret source=app-key,target=app,uid=1000,gid=1001,mode=0400 \
|
||||||
redis:3.0.6
|
redis:7.4.1
|
||||||
|
|
||||||
4cdgfyky7ozwh3htjfw0d12qv
|
4cdgfyky7ozwh3htjfw0d12qv
|
||||||
```
|
```
|
||||||
|
@ -215,14 +215,14 @@ pre-exist in the container. The `mode` is specified as a 4-number sequence such
|
||||||
as `0755`.
|
as `0755`.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker service create --name=redis --config redis-conf redis:3.0.6
|
$ docker service create --name=redis --config redis-conf redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a service with a config and specify the target location and file mode:
|
Create a service with a config and specify the target location and file mode:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker service create --name redis \
|
$ docker service create --name redis \
|
||||||
--config source=redis-conf,target=/etc/redis/redis.conf,mode=0400 redis:3.0.6
|
--config source=redis-conf,target=/etc/redis/redis.conf,mode=0400 redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
To grant a service access to multiple configs, use multiple `--config` flags.
|
To grant a service access to multiple configs, use multiple `--config` flags.
|
||||||
|
@ -239,7 +239,7 @@ $ docker service create \
|
||||||
--name redis \
|
--name redis \
|
||||||
--update-delay 10s \
|
--update-delay 10s \
|
||||||
--update-parallelism 2 \
|
--update-parallelism 2 \
|
||||||
redis:3.0.6
|
redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
When you run a [service update](service_update.md), the scheduler updates a
|
When you run a [service update](service_update.md), the scheduler updates a
|
||||||
|
@ -256,7 +256,7 @@ $ docker service create \
|
||||||
--name redis_2 \
|
--name redis_2 \
|
||||||
--replicas 5 \
|
--replicas 5 \
|
||||||
--env MYVAR=foo \
|
--env MYVAR=foo \
|
||||||
redis:3.0.6
|
redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
To specify multiple environment variables, specify multiple `--env` flags, each
|
To specify multiple environment variables, specify multiple `--env` flags, each
|
||||||
|
@ -268,7 +268,7 @@ $ docker service create \
|
||||||
--replicas 5 \
|
--replicas 5 \
|
||||||
--env MYVAR=foo \
|
--env MYVAR=foo \
|
||||||
--env MYVAR2=bar \
|
--env MYVAR2=bar \
|
||||||
redis:3.0.6
|
redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
### <a name="hostname"></a> Create a service with specific hostname (--hostname)
|
### <a name="hostname"></a> Create a service with specific hostname (--hostname)
|
||||||
|
@ -277,7 +277,7 @@ This option sets the docker service containers hostname to a specific string.
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker service create --name redis --hostname myredis redis:3.0.6
|
$ docker service create --name redis --hostname myredis redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
### <a name="label"></a> Set metadata on a service (-l, --label)
|
### <a name="label"></a> Set metadata on a service (-l, --label)
|
||||||
|
@ -290,7 +290,7 @@ $ docker service create \
|
||||||
--name redis_2 \
|
--name redis_2 \
|
||||||
--label com.example.foo="bar" \
|
--label com.example.foo="bar" \
|
||||||
--label bar=baz \
|
--label bar=baz \
|
||||||
redis:3.0.6
|
redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
For more information about labels, refer to [apply custom
|
For more information about labels, refer to [apply custom
|
||||||
|
@ -679,7 +679,7 @@ The following command creates a global service:
|
||||||
$ docker service create \
|
$ docker service create \
|
||||||
--name redis_2 \
|
--name redis_2 \
|
||||||
--mode global \
|
--mode global \
|
||||||
redis:3.0.6
|
redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
### <a name="constraint"></a> Specify service constraints (--constraint)
|
### <a name="constraint"></a> Specify service constraints (--constraint)
|
||||||
|
@ -712,7 +712,7 @@ $ docker service create \
|
||||||
--name redis_2 \
|
--name redis_2 \
|
||||||
--constraint node.platform.os==linux \
|
--constraint node.platform.os==linux \
|
||||||
--constraint node.labels.type==queue \
|
--constraint node.labels.type==queue \
|
||||||
redis:3.0.6
|
redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
If the service constraints exclude all nodes in the cluster, a message is printed
|
If the service constraints exclude all nodes in the cluster, a message is printed
|
||||||
|
@ -760,7 +760,7 @@ $ docker service create \
|
||||||
--replicas 9 \
|
--replicas 9 \
|
||||||
--name redis_2 \
|
--name redis_2 \
|
||||||
--placement-pref spread=node.labels.datacenter \
|
--placement-pref spread=node.labels.datacenter \
|
||||||
redis:3.0.6
|
redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
This uses `--placement-pref` with a `spread` strategy (currently the only
|
This uses `--placement-pref` with a `spread` strategy (currently the only
|
||||||
|
@ -812,7 +812,7 @@ $ docker service create \
|
||||||
--name redis_2 \
|
--name redis_2 \
|
||||||
--placement-pref 'spread=node.labels.datacenter' \
|
--placement-pref 'spread=node.labels.datacenter' \
|
||||||
--placement-pref 'spread=node.labels.rack' \
|
--placement-pref 'spread=node.labels.rack' \
|
||||||
redis:3.0.6
|
redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
When updating a service with `docker service update`, `--placement-pref-add`
|
When updating a service with `docker service update`, `--placement-pref-add`
|
||||||
|
|
|
@ -40,7 +40,7 @@ For example, given the following service;
|
||||||
```console
|
```console
|
||||||
$ docker service ls
|
$ docker service ls
|
||||||
ID NAME MODE REPLICAS IMAGE
|
ID NAME MODE REPLICAS IMAGE
|
||||||
dmu1ept4cxcf redis replicated 3/3 redis:3.0.6
|
dmu1ept4cxcf redis replicated 3/3 redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
Both `docker service inspect redis`, and `docker service inspect dmu1ept4cxcf`
|
Both `docker service inspect redis`, and `docker service inspect dmu1ept4cxcf`
|
||||||
|
@ -65,7 +65,7 @@ The output is in JSON format, for example:
|
||||||
"Name": "redis",
|
"Name": "redis",
|
||||||
"TaskTemplate": {
|
"TaskTemplate": {
|
||||||
"ContainerSpec": {
|
"ContainerSpec": {
|
||||||
"Image": "redis:3.0.6"
|
"Image": "redis:7.4.1"
|
||||||
},
|
},
|
||||||
"Resources": {
|
"Resources": {
|
||||||
"Limits": {},
|
"Limits": {},
|
||||||
|
|
|
@ -37,7 +37,7 @@ $ docker service ls
|
||||||
|
|
||||||
ID NAME MODE REPLICAS IMAGE
|
ID NAME MODE REPLICAS IMAGE
|
||||||
c8wgl7q4ndfd frontend replicated 5/5 nginx:alpine
|
c8wgl7q4ndfd frontend replicated 5/5 nginx:alpine
|
||||||
dmu1ept4cxcf redis replicated 3/3 redis:3.0.6
|
dmu1ept4cxcf redis replicated 3/3 redis:7.4.1
|
||||||
iwe3278osahj mongo global 7/7 mongo:3.3
|
iwe3278osahj mongo global 7/7 mongo:3.3
|
||||||
hh08h9uu8uwr job replicated-job 1/1 (3/5 completed) nginx:latest
|
hh08h9uu8uwr job replicated-job 1/1 (3/5 completed) nginx:latest
|
||||||
```
|
```
|
||||||
|
@ -68,7 +68,7 @@ The following filter matches services with an ID starting with `0bcjw`:
|
||||||
```console
|
```console
|
||||||
$ docker service ls -f "id=0bcjw"
|
$ docker service ls -f "id=0bcjw"
|
||||||
ID NAME MODE REPLICAS IMAGE
|
ID NAME MODE REPLICAS IMAGE
|
||||||
0bcjwfh8ychr redis replicated 1/1 redis:3.0.6
|
0bcjwfh8ychr redis replicated 1/1 redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
#### label
|
#### label
|
||||||
|
@ -84,7 +84,7 @@ $ docker service ls --filter label=project
|
||||||
ID NAME MODE REPLICAS IMAGE
|
ID NAME MODE REPLICAS IMAGE
|
||||||
01sl1rp6nj5u frontend2 replicated 1/1 nginx:alpine
|
01sl1rp6nj5u frontend2 replicated 1/1 nginx:alpine
|
||||||
36xvvwwauej0 frontend replicated 5/5 nginx:alpine
|
36xvvwwauej0 frontend replicated 5/5 nginx:alpine
|
||||||
74nzcxxjv6fq backend replicated 3/3 redis:3.0.6
|
74nzcxxjv6fq backend replicated 3/3 redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
The following filter matches only services with the `project` label with the
|
The following filter matches only services with the `project` label with the
|
||||||
|
@ -94,7 +94,7 @@ The following filter matches only services with the `project` label with the
|
||||||
$ docker service ls --filter label=project=project-a
|
$ docker service ls --filter label=project=project-a
|
||||||
ID NAME MODE REPLICAS IMAGE
|
ID NAME MODE REPLICAS IMAGE
|
||||||
36xvvwwauej0 frontend replicated 5/5 nginx:alpine
|
36xvvwwauej0 frontend replicated 5/5 nginx:alpine
|
||||||
74nzcxxjv6fq backend replicated 3/3 redis:3.0.6
|
74nzcxxjv6fq backend replicated 3/3 redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
#### mode
|
#### mode
|
||||||
|
@ -118,7 +118,7 @@ The following filter matches services with a name starting with `redis`.
|
||||||
```console
|
```console
|
||||||
$ docker service ls --filter name=redis
|
$ docker service ls --filter name=redis
|
||||||
ID NAME MODE REPLICAS IMAGE
|
ID NAME MODE REPLICAS IMAGE
|
||||||
0bcjwfh8ychr redis replicated 1/1 redis:3.0.6
|
0bcjwfh8ychr redis replicated 1/1 redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
### <a name="format"></a> Format the output (--format)
|
### <a name="format"></a> Format the output (--format)
|
||||||
|
|
|
@ -36,35 +36,35 @@ The following command shows all the tasks that are part of the `redis` service:
|
||||||
$ docker service ps redis
|
$ docker service ps redis
|
||||||
|
|
||||||
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||||
0qihejybwf1x redis.1 redis:3.0.5 manager1 Running Running 8 seconds
|
0qihejybwf1x redis.1 redis:7.4.0 manager1 Running Running 8 seconds
|
||||||
bk658fpbex0d redis.2 redis:3.0.5 worker2 Running Running 9 seconds
|
bk658fpbex0d redis.2 redis:7.4.0 worker2 Running Running 9 seconds
|
||||||
5ls5s5fldaqg redis.3 redis:3.0.5 worker1 Running Running 9 seconds
|
5ls5s5fldaqg redis.3 redis:7.4.0 worker1 Running Running 9 seconds
|
||||||
8ryt076polmc redis.4 redis:3.0.5 worker1 Running Running 9 seconds
|
8ryt076polmc redis.4 redis:7.4.0 worker1 Running Running 9 seconds
|
||||||
1x0v8yomsncd redis.5 redis:3.0.5 manager1 Running Running 8 seconds
|
1x0v8yomsncd redis.5 redis:7.4.0 manager1 Running Running 8 seconds
|
||||||
71v7je3el7rr redis.6 redis:3.0.5 worker2 Running Running 9 seconds
|
71v7je3el7rr redis.6 redis:7.4.0 worker2 Running Running 9 seconds
|
||||||
4l3zm9b7tfr7 redis.7 redis:3.0.5 worker2 Running Running 9 seconds
|
4l3zm9b7tfr7 redis.7 redis:7.4.0 worker2 Running Running 9 seconds
|
||||||
9tfpyixiy2i7 redis.8 redis:3.0.5 worker1 Running Running 9 seconds
|
9tfpyixiy2i7 redis.8 redis:7.4.0 worker1 Running Running 9 seconds
|
||||||
3w1wu13yupln redis.9 redis:3.0.5 manager1 Running Running 8 seconds
|
3w1wu13yupln redis.9 redis:7.4.0 manager1 Running Running 8 seconds
|
||||||
8eaxrb2fqpbn redis.10 redis:3.0.5 manager1 Running Running 8 seconds
|
8eaxrb2fqpbn redis.10 redis:7.4.0 manager1 Running Running 8 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
In addition to running tasks, the output also shows the task history. For
|
In addition to running tasks, the output also shows the task history. For
|
||||||
example, after updating the service to use the `redis:3.0.6` image, the output
|
example, after updating the service to use the `redis:7.4.1` image, the output
|
||||||
may look like this:
|
may look like this:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ docker service ps redis
|
$ docker service ps redis
|
||||||
|
|
||||||
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||||
50qe8lfnxaxk redis.1 redis:3.0.6 manager1 Running Running 6 seconds ago
|
50qe8lfnxaxk redis.1 redis:7.4.1 manager1 Running Running 6 seconds ago
|
||||||
ky2re9oz86r9 \_ redis.1 redis:3.0.5 manager1 Shutdown Shutdown 8 seconds ago
|
ky2re9oz86r9 \_ redis.1 redis:7.4.0 manager1 Shutdown Shutdown 8 seconds ago
|
||||||
3s46te2nzl4i redis.2 redis:3.0.6 worker2 Running Running less than a second ago
|
3s46te2nzl4i redis.2 redis:7.4.1 worker2 Running Running less than a second ago
|
||||||
nvjljf7rmor4 \_ redis.2 redis:3.0.6 worker2 Shutdown Rejected 23 seconds ago "No such image: redis@sha256:6…"
|
nvjljf7rmor4 \_ redis.2 redis:7.4.1 worker2 Shutdown Rejected 23 seconds ago "No such image: redis@sha256:6…"
|
||||||
vtiuz2fpc0yb \_ redis.2 redis:3.0.5 worker2 Shutdown Shutdown 1 second ago
|
vtiuz2fpc0yb \_ redis.2 redis:7.4.0 worker2 Shutdown Shutdown 1 second ago
|
||||||
jnarweeha8x4 redis.3 redis:3.0.6 worker1 Running Running 3 seconds ago
|
jnarweeha8x4 redis.3 redis:7.4.1 worker1 Running Running 3 seconds ago
|
||||||
vs448yca2nz4 \_ redis.3 redis:3.0.5 worker1 Shutdown Shutdown 4 seconds ago
|
vs448yca2nz4 \_ redis.3 redis:7.4.0 worker1 Shutdown Shutdown 4 seconds ago
|
||||||
jf1i992619ir redis.4 redis:3.0.6 worker1 Running Running 10 seconds ago
|
jf1i992619ir redis.4 redis:7.4.1 worker1 Running Running 10 seconds ago
|
||||||
blkttv7zs8ee \_ redis.4 redis:3.0.5 worker1 Shutdown Shutdown 11 seconds ago
|
blkttv7zs8ee \_ redis.4 redis:7.4.0 worker1 Shutdown Shutdown 11 seconds ago
|
||||||
```
|
```
|
||||||
|
|
||||||
The number of items in the task history is determined by the
|
The number of items in the task history is determined by the
|
||||||
|
@ -82,10 +82,10 @@ example:
|
||||||
$ docker service ps --no-trunc redis
|
$ docker service ps --no-trunc redis
|
||||||
|
|
||||||
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||||
50qe8lfnxaxksi9w2a704wkp7 redis.1 redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842 manager1 Running Running 5 minutes ago
|
50qe8lfnxaxksi9w2a704wkp7 redis.1 redis:7.4.1@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842 manager1 Running Running 5 minutes ago
|
||||||
ky2re9oz86r9556i2szb8a8af \_ redis.1 redis:3.0.5@sha256:f8829e00d95672c48c60f468329d6693c4bdd28d1f057e755f8ba8b40008682e worker2 Shutdown Shutdown 5 minutes ago
|
ky2re9oz86r9556i2szb8a8af \_ redis.1 redis:7.4.0@sha256:f8829e00d95672c48c60f468329d6693c4bdd28d1f057e755f8ba8b40008682e worker2 Shutdown Shutdown 5 minutes ago
|
||||||
bk658fpbex0d57cqcwoe3jthu redis.2 redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842 worker2 Running Running 5 seconds
|
bk658fpbex0d57cqcwoe3jthu redis.2 redis:7.4.1@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842 worker2 Running Running 5 seconds
|
||||||
nvjljf7rmor4htv7l8rwcx7i7 \_ redis.2 redis:3.0.6@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842 worker2 Shutdown Rejected 5 minutes ago "No such image: redis@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842"
|
nvjljf7rmor4htv7l8rwcx7i7 \_ redis.2 redis:7.4.1@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842 worker2 Shutdown Rejected 5 minutes ago "No such image: redis@sha256:6a692a76c2081888b589e26e6ec835743119fe453d67ecf03df7de5b73d69842"
|
||||||
```
|
```
|
||||||
|
|
||||||
### <a name="filter"></a> Filtering (--filter)
|
### <a name="filter"></a> Filtering (--filter)
|
||||||
|
@ -111,8 +111,8 @@ The `id` filter matches on all or a prefix of a task's ID.
|
||||||
$ docker service ps -f "id=8" redis
|
$ docker service ps -f "id=8" redis
|
||||||
|
|
||||||
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||||
8ryt076polmc redis.4 redis:3.0.6 worker1 Running Running 9 seconds
|
8ryt076polmc redis.4 redis:7.4.1 worker1 Running Running 9 seconds
|
||||||
8eaxrb2fqpbn redis.10 redis:3.0.6 manager1 Running Running 8 seconds
|
8eaxrb2fqpbn redis.10 redis:7.4.1 manager1 Running Running 8 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
#### name
|
#### name
|
||||||
|
@ -123,7 +123,7 @@ The `name` filter matches on task names.
|
||||||
$ docker service ps -f "name=redis.1" redis
|
$ docker service ps -f "name=redis.1" redis
|
||||||
|
|
||||||
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||||
qihejybwf1x5 redis.1 redis:3.0.6 manager1 Running Running 8 seconds
|
qihejybwf1x5 redis.1 redis:7.4.1 manager1 Running Running 8 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -135,10 +135,10 @@ The `node` filter matches on a node name or a node ID.
|
||||||
$ docker service ps -f "node=manager1" redis
|
$ docker service ps -f "node=manager1" redis
|
||||||
|
|
||||||
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR PORTS
|
||||||
0qihejybwf1x redis.1 redis:3.0.6 manager1 Running Running 8 seconds
|
0qihejybwf1x redis.1 redis:7.4.1 manager1 Running Running 8 seconds
|
||||||
1x0v8yomsncd redis.5 redis:3.0.6 manager1 Running Running 8 seconds
|
1x0v8yomsncd redis.5 redis:7.4.1 manager1 Running Running 8 seconds
|
||||||
3w1wu13yupln redis.9 redis:3.0.6 manager1 Running Running 8 seconds
|
3w1wu13yupln redis.9 redis:7.4.1 manager1 Running Running 8 seconds
|
||||||
8eaxrb2fqpbn redis.10 redis:3.0.6 manager1 Running Running 8 seconds
|
8eaxrb2fqpbn redis.10 redis:7.4.1 manager1 Running Running 8 seconds
|
||||||
```
|
```
|
||||||
|
|
||||||
#### desired-state
|
#### desired-state
|
||||||
|
|
|
@ -84,7 +84,7 @@ $ docker service ls
|
||||||
|
|
||||||
ID NAME MODE REPLICAS IMAGE
|
ID NAME MODE REPLICAS IMAGE
|
||||||
3pr5mlvu3fh9 frontend replicated 5/5 nginx:alpine
|
3pr5mlvu3fh9 frontend replicated 5/5 nginx:alpine
|
||||||
74nzcxxjv6fq backend replicated 3/3 redis:3.0.6
|
74nzcxxjv6fq backend replicated 3/3 redis:7.4.1
|
||||||
```
|
```
|
||||||
|
|
||||||
## Related commands
|
## Related commands
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG GO_VERSION=1.23.2
|
ARG GO_VERSION=1.23.3
|
||||||
|
|
||||||
FROM golang:${GO_VERSION}-alpine AS generated
|
FROM golang:${GO_VERSION}-alpine AS generated
|
||||||
ENV GOTOOLCHAIN=local
|
ENV GOTOOLCHAIN=local
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
// FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16:
|
||||||
//go:build go1.21
|
//go:build go1.22
|
||||||
|
|
||||||
package templates
|
package templates
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue