Merge pull request #82 from vdemeester/opts-from-docker

Import `opts` package from moby/moby
This commit is contained in:
Kenfe-Mickaël Laventure 2017-05-15 11:38:59 -07:00 committed by GitHub
commit 36e557f1a3
70 changed files with 3412 additions and 479 deletions

View File

@ -12,11 +12,11 @@ import (
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/credentials"
cliflags "github.com/docker/cli/cli/flags"
dopts "github.com/docker/cli/opts"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
dopts "github.com/docker/docker/opts"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
"github.com/docker/notary/passphrase"

View File

@ -7,8 +7,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/system"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"

View File

@ -4,8 +4,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
@ -17,7 +17,7 @@ type listOptions struct {
}
func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
opts := listOptions{filter: opts.NewFilterOpt()}
listOpts := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ls [OPTIONS]",
@ -25,30 +25,30 @@ func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
Short: "List configs",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runConfigList(dockerCli, opts)
return runConfigList(dockerCli, listOpts)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVarP(&opts.format, "format", "", "", "Pretty-print configs using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&listOpts.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVarP(&listOpts.format, "format", "", "", "Pretty-print configs using a Go template")
flags.VarP(&listOpts.filter, "filter", "f", "Filter output based on conditions provided")
return cmd
}
func runConfigList(dockerCli command.Cli, opts listOptions) error {
func runConfigList(dockerCli command.Cli, options listOptions) error {
client := dockerCli.Client()
ctx := context.Background()
configs, err := client.ConfigList(ctx, types.ConfigListOptions{Filters: opts.filter.Value()})
configs, err := client.ConfigList(ctx, types.ConfigListOptions{Filters: options.filter.Value()})
if err != nil {
return err
}
format := opts.format
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().ConfigFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().ConfigFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().ConfigFormat
} else {
format = formatter.TableFormatKey
@ -57,7 +57,7 @@ func runConfigList(dockerCli command.Cli, opts listOptions) error {
configCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewConfigFormat(format, opts.quiet),
Format: formatter.NewConfigFormat(format, options.quiet),
}
return formatter.ConfigWrite(configCtx, configs)
}

View File

@ -5,8 +5,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
dockeropts "github.com/docker/docker/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
@ -18,54 +18,54 @@ type commitOptions struct {
pause bool
comment string
author string
changes dockeropts.ListOpts
changes opts.ListOpts
}
// NewCommitCommand creates a new cobra.Command for `docker commit`
func NewCommitCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts commitOptions
var options commitOptions
cmd := &cobra.Command{
Use: "commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]",
Short: "Create a new image from a container's changes",
Args: cli.RequiresRangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
opts.container = args[0]
options.container = args[0]
if len(args) > 1 {
opts.reference = args[1]
options.reference = args[1]
}
return runCommit(dockerCli, &opts)
return runCommit(dockerCli, &options)
},
}
flags := cmd.Flags()
flags.SetInterspersed(false)
flags.BoolVarP(&opts.pause, "pause", "p", true, "Pause container during commit")
flags.StringVarP(&opts.comment, "message", "m", "", "Commit message")
flags.StringVarP(&opts.author, "author", "a", "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
flags.BoolVarP(&options.pause, "pause", "p", true, "Pause container during commit")
flags.StringVarP(&options.comment, "message", "m", "", "Commit message")
flags.StringVarP(&options.author, "author", "a", "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
opts.changes = dockeropts.NewListOpts(nil)
flags.VarP(&opts.changes, "change", "c", "Apply Dockerfile instruction to the created image")
options.changes = opts.NewListOpts(nil)
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image")
return cmd
}
func runCommit(dockerCli *command.DockerCli, opts *commitOptions) error {
func runCommit(dockerCli *command.DockerCli, options *commitOptions) error {
ctx := context.Background()
name := opts.container
reference := opts.reference
name := options.container
reference := options.reference
options := types.ContainerCommitOptions{
commitOptions := types.ContainerCommitOptions{
Reference: reference,
Comment: opts.comment,
Author: opts.author,
Changes: opts.changes.GetAll(),
Pause: opts.pause,
Comment: options.comment,
Author: options.author,
Changes: options.changes.GetAll(),
Pause: options.pause,
}
response, err := dockerCli.Client().ContainerCommit(ctx, name, options)
response, err := dockerCli.Client().ContainerCommit(ctx, name, commitOptions)
if err != nil {
return err
}

View File

@ -7,9 +7,9 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
apiclient "github.com/docker/docker/client"
options "github.com/docker/docker/opts"
"github.com/docker/docker/pkg/promise"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -22,19 +22,19 @@ type execOptions struct {
detach bool
user string
privileged bool
env *options.ListOpts
env *opts.ListOpts
}
func newExecOptions() *execOptions {
var values []string
return &execOptions{
env: options.NewListOptsRef(&values, options.ValidateEnv),
env: opts.NewListOptsRef(&values, opts.ValidateEnv),
}
}
// NewExecCommand creates a new cobra.Command for `docker exec`
func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := newExecOptions()
options := newExecOptions()
cmd := &cobra.Command{
Use: "exec [OPTIONS] CONTAINER COMMAND [ARG...]",
@ -43,35 +43,35 @@ func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command {
RunE: func(cmd *cobra.Command, args []string) error {
container := args[0]
execCmd := args[1:]
return runExec(dockerCli, opts, container, execCmd)
return runExec(dockerCli, options, container, execCmd)
},
}
flags := cmd.Flags()
flags.SetInterspersed(false)
flags.StringVarP(&opts.detachKeys, "detach-keys", "", "", "Override the key sequence for detaching a container")
flags.BoolVarP(&opts.interactive, "interactive", "i", false, "Keep STDIN open even if not attached")
flags.BoolVarP(&opts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
flags.BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: run command in the background")
flags.StringVarP(&opts.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
flags.BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the command")
flags.VarP(opts.env, "env", "e", "Set environment variables")
flags.StringVarP(&options.detachKeys, "detach-keys", "", "", "Override the key sequence for detaching a container")
flags.BoolVarP(&options.interactive, "interactive", "i", false, "Keep STDIN open even if not attached")
flags.BoolVarP(&options.tty, "tty", "t", false, "Allocate a pseudo-TTY")
flags.BoolVarP(&options.detach, "detach", "d", false, "Detached mode: run command in the background")
flags.StringVarP(&options.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
flags.BoolVarP(&options.privileged, "privileged", "", false, "Give extended privileges to the command")
flags.VarP(options.env, "env", "e", "Set environment variables")
flags.SetAnnotation("env", "version", []string{"1.25"})
return cmd
}
// nolint: gocyclo
func runExec(dockerCli *command.DockerCli, opts *execOptions, container string, execCmd []string) error {
execConfig, err := parseExec(opts, execCmd)
func runExec(dockerCli *command.DockerCli, options *execOptions, container string, execCmd []string) error {
execConfig, err := parseExec(options, execCmd)
// just in case the ParseExec does not exit
if container == "" || err != nil {
return cli.StatusError{StatusCode: 1}
}
if opts.detachKeys != "" {
dockerCli.ConfigFile().DetachKeys = opts.detachKeys
if options.detachKeys != "" {
dockerCli.ConfigFile().DetachKeys = options.detachKeys
}
// Send client escape keys

View File

@ -6,8 +6,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/templates"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -26,27 +26,27 @@ type psOptions struct {
// NewPsCommand creates a new cobra.Command for `docker ps`
func NewPsCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := psOptions{filter: opts.NewFilterOpt()}
options := psOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ps [OPTIONS]",
Short: "List containers",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runPs(dockerCli, &opts)
return runPs(dockerCli, &options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display numeric IDs")
flags.BoolVarP(&opts.size, "size", "s", false, "Display total file sizes")
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
flags.BoolVarP(&opts.nLatest, "latest", "l", false, "Show the latest created container (includes all states)")
flags.IntVarP(&opts.last, "last", "n", -1, "Show n last created containers (includes all states)")
flags.StringVarP(&opts.format, "format", "", "", "Pretty-print containers using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display numeric IDs")
flags.BoolVarP(&options.size, "size", "s", false, "Display total file sizes")
flags.BoolVarP(&options.all, "all", "a", false, "Show all containers (default shows just running)")
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
flags.BoolVarP(&options.nLatest, "latest", "l", false, "Show the latest created container (includes all states)")
flags.IntVarP(&options.last, "last", "n", -1, "Show n last created containers (includes all states)")
flags.StringVarP(&options.format, "format", "", "", "Pretty-print containers using a Go template")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
return cmd
}
@ -109,10 +109,10 @@ func buildContainerListOptions(opts *psOptions) (*types.ContainerListOptions, er
return options, nil
}
func runPs(dockerCli *command.DockerCli, opts *psOptions) error {
func runPs(dockerCli *command.DockerCli, options *psOptions) error {
ctx := context.Background()
listOptions, err := buildContainerListOptions(opts)
listOptions, err := buildContainerListOptions(options)
if err != nil {
return err
}
@ -122,9 +122,9 @@ func runPs(dockerCli *command.DockerCli, opts *psOptions) error {
return err
}
format := opts.format
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().PsFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().PsFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().PsFormat
} else {
format = formatter.TableFormatKey
@ -133,8 +133,8 @@ func runPs(dockerCli *command.DockerCli, opts *psOptions) error {
containerCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewContainerFormat(format, opts.quiet, listOptions.Size),
Trunc: !opts.noTrunc,
Format: formatter.NewContainerFormat(format, options.quiet, listOptions.Size),
Trunc: !options.noTrunc,
}
return formatter.ContainerWrite(containerCtx, containers)
}

View File

@ -12,10 +12,10 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/signal"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/go-connections/nat"

View File

@ -5,7 +5,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/opts"
"github.com/docker/cli/opts"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -18,14 +18,14 @@ type pruneOptions struct {
// NewPruneCommand returns a new cobra prune command for containers
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
opts := pruneOptions{filter: opts.NewFilterOpt()}
options := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "prune [OPTIONS]",
Short: "Remove all stopped containers",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
spaceReclaimed, output, err := runPrune(dockerCli, opts)
spaceReclaimed, output, err := runPrune(dockerCli, options)
if err != nil {
return err
}
@ -39,8 +39,8 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
}
flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
return cmd
}
@ -48,10 +48,10 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
const warning = `WARNING! This will remove all stopped containers.
Are you sure you want to continue?`
func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := command.PruneFilters(dockerCli, opts.filter.Value())
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return
}

View File

@ -3,7 +3,7 @@ package container
import (
"testing"
"github.com/docker/docker/opts"
"github.com/docker/cli/opts"
"github.com/stretchr/testify/assert"
)

View File

@ -6,8 +6,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/opts"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -37,71 +37,71 @@ type updateOptions struct {
// NewUpdateCommand creates a new cobra.Command for `docker update`
func NewUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts updateOptions
var options updateOptions
cmd := &cobra.Command{
Use: "update [OPTIONS] CONTAINER [CONTAINER...]",
Short: "Update configuration of one or more containers",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.containers = args
opts.nFlag = cmd.Flags().NFlag()
return runUpdate(dockerCli, &opts)
options.containers = args
options.nFlag = cmd.Flags().NFlag()
return runUpdate(dockerCli, &options)
},
}
flags := cmd.Flags()
flags.Uint16Var(&opts.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)")
flags.Int64Var(&opts.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
flags.Int64Var(&opts.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
flags.Int64Var(&opts.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit the CPU real-time period in microseconds")
flags.Uint16Var(&options.blkioWeight, "blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)")
flags.Int64Var(&options.cpuPeriod, "cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
flags.Int64Var(&options.cpuQuota, "cpu-quota", 0, "Limit CPU CFS (Completely Fair Scheduler) quota")
flags.Int64Var(&options.cpuRealtimePeriod, "cpu-rt-period", 0, "Limit the CPU real-time period in microseconds")
flags.SetAnnotation("cpu-rt-period", "version", []string{"1.25"})
flags.Int64Var(&opts.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit the CPU real-time runtime in microseconds")
flags.Int64Var(&options.cpuRealtimeRuntime, "cpu-rt-runtime", 0, "Limit the CPU real-time runtime in microseconds")
flags.SetAnnotation("cpu-rt-runtime", "version", []string{"1.25"})
flags.StringVar(&opts.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
flags.StringVar(&opts.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
flags.Int64VarP(&opts.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
flags.VarP(&opts.memory, "memory", "m", "Memory limit")
flags.Var(&opts.memoryReservation, "memory-reservation", "Memory soft limit")
flags.Var(&opts.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
flags.Var(&opts.kernelMemory, "kernel-memory", "Kernel memory limit")
flags.StringVar(&opts.restartPolicy, "restart", "", "Restart policy to apply when a container exits")
flags.StringVar(&options.cpusetCpus, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
flags.StringVar(&options.cpusetMems, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
flags.Int64VarP(&options.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
flags.VarP(&options.memory, "memory", "m", "Memory limit")
flags.Var(&options.memoryReservation, "memory-reservation", "Memory soft limit")
flags.Var(&options.memorySwap, "memory-swap", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
flags.Var(&options.kernelMemory, "kernel-memory", "Kernel memory limit")
flags.StringVar(&options.restartPolicy, "restart", "", "Restart policy to apply when a container exits")
flags.Var(&opts.cpus, "cpus", "Number of CPUs")
flags.Var(&options.cpus, "cpus", "Number of CPUs")
flags.SetAnnotation("cpus", "version", []string{"1.29"})
return cmd
}
func runUpdate(dockerCli *command.DockerCli, opts *updateOptions) error {
func runUpdate(dockerCli *command.DockerCli, options *updateOptions) error {
var err error
if opts.nFlag == 0 {
if options.nFlag == 0 {
return errors.New("you must provide one or more flags when using this command")
}
var restartPolicy containertypes.RestartPolicy
if opts.restartPolicy != "" {
restartPolicy, err = runconfigopts.ParseRestartPolicy(opts.restartPolicy)
if options.restartPolicy != "" {
restartPolicy, err = runconfigopts.ParseRestartPolicy(options.restartPolicy)
if err != nil {
return err
}
}
resources := containertypes.Resources{
BlkioWeight: opts.blkioWeight,
CpusetCpus: opts.cpusetCpus,
CpusetMems: opts.cpusetMems,
CPUShares: opts.cpuShares,
Memory: opts.memory.Value(),
MemoryReservation: opts.memoryReservation.Value(),
MemorySwap: opts.memorySwap.Value(),
KernelMemory: opts.kernelMemory.Value(),
CPUPeriod: opts.cpuPeriod,
CPUQuota: opts.cpuQuota,
CPURealtimePeriod: opts.cpuRealtimePeriod,
CPURealtimeRuntime: opts.cpuRealtimeRuntime,
NanoCPUs: opts.cpus.Value(),
BlkioWeight: options.blkioWeight,
CpusetCpus: options.cpusetCpus,
CpusetMems: options.cpusetMems,
CPUShares: options.cpuShares,
Memory: options.memory.Value(),
MemoryReservation: options.memoryReservation.Value(),
MemorySwap: options.memorySwap.Value(),
KernelMemory: options.kernelMemory.Value(),
CPUPeriod: options.cpuPeriod,
CPUQuota: options.cpuQuota,
CPURealtimePeriod: options.cpuRealtimePeriod,
CPURealtimeRuntime: options.cpuRealtimeRuntime,
NanoCPUs: options.cpus.Value(),
}
updateConfig := containertypes.UpdateConfig{
@ -115,7 +115,7 @@ func runUpdate(dockerCli *command.DockerCli, opts *updateOptions) error {
warns []string
errs []string
)
for _, container := range opts.containers {
for _, container := range options.containers {
r, err := dockerCli.Client().ContainerUpdate(ctx, container, updateConfig)
if err != nil {
errs = append(errs, err.Error())

View File

@ -15,11 +15,11 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image/build"
"github.com/docker/cli/opts"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/progress"

View File

@ -4,15 +4,14 @@ import (
"io"
"os"
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
dockeropts "github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
dockeropts "github.com/docker/docker/opts"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/urlutil"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type importOptions struct {
@ -24,41 +23,41 @@ type importOptions struct {
// NewImportCommand creates a new `docker import` command
func NewImportCommand(dockerCli command.Cli) *cobra.Command {
var opts importOptions
var options importOptions
cmd := &cobra.Command{
Use: "import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]",
Short: "Import the contents from a tarball to create a filesystem image",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.source = args[0]
options.source = args[0]
if len(args) > 1 {
opts.reference = args[1]
options.reference = args[1]
}
return runImport(dockerCli, opts)
return runImport(dockerCli, options)
},
}
flags := cmd.Flags()
opts.changes = dockeropts.NewListOpts(nil)
flags.VarP(&opts.changes, "change", "c", "Apply Dockerfile instruction to the created image")
flags.StringVarP(&opts.message, "message", "m", "", "Set commit message for imported image")
options.changes = dockeropts.NewListOpts(nil)
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image")
flags.StringVarP(&options.message, "message", "m", "", "Set commit message for imported image")
return cmd
}
func runImport(dockerCli command.Cli, opts importOptions) error {
func runImport(dockerCli command.Cli, options importOptions) error {
var (
in io.Reader
srcName = opts.source
srcName = options.source
)
if opts.source == "-" {
if options.source == "-" {
in = dockerCli.In()
} else if !urlutil.IsURL(opts.source) {
} else if !urlutil.IsURL(options.source) {
srcName = "-"
file, err := os.Open(opts.source)
file, err := os.Open(options.source)
if err != nil {
return err
}
@ -71,14 +70,14 @@ func runImport(dockerCli command.Cli, opts importOptions) error {
SourceName: srcName,
}
options := types.ImageImportOptions{
Message: opts.message,
Changes: opts.changes.GetAll(),
importOptions := types.ImageImportOptions{
Message: options.message,
Changes: options.changes.GetAll(),
}
clnt := dockerCli.Client()
responseBody, err := clnt.ImageImport(context.Background(), source, opts.reference, options)
responseBody, err := clnt.ImageImport(context.Background(), source, options.reference, importOptions)
if err != nil {
return err
}

View File

@ -1,14 +1,13 @@
package image
import (
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type imagesOptions struct {
@ -24,7 +23,7 @@ type imagesOptions struct {
// NewImagesCommand creates a new `docker images` command
func NewImagesCommand(dockerCli command.Cli) *cobra.Command {
opts := imagesOptions{filter: opts.NewFilterOpt()}
options := imagesOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "images [OPTIONS] [REPOSITORY[:TAG]]",
@ -32,20 +31,20 @@ func NewImagesCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) > 0 {
opts.matchName = args[0]
options.matchName = args[0]
}
return runImages(dockerCli, opts)
return runImages(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only show numeric IDs")
flags.BoolVarP(&opts.all, "all", "a", false, "Show all images (default hides intermediate images)")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
flags.BoolVar(&opts.showDigests, "digests", false, "Show digests")
flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show numeric IDs")
flags.BoolVarP(&options.all, "all", "a", false, "Show all images (default hides intermediate images)")
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
flags.BoolVar(&options.showDigests, "digests", false, "Show digests")
flags.StringVar(&options.format, "format", "", "Pretty-print images using a Go template")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
return cmd
}
@ -57,27 +56,27 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
return &cmd
}
func runImages(dockerCli command.Cli, opts imagesOptions) error {
func runImages(dockerCli command.Cli, options imagesOptions) error {
ctx := context.Background()
filters := opts.filter.Value()
if opts.matchName != "" {
filters.Add("reference", opts.matchName)
filters := options.filter.Value()
if options.matchName != "" {
filters.Add("reference", options.matchName)
}
options := types.ImageListOptions{
All: opts.all,
listOptions := types.ImageListOptions{
All: options.all,
Filters: filters,
}
images, err := dockerCli.Client().ImageList(ctx, options)
images, err := dockerCli.Client().ImageList(ctx, listOptions)
if err != nil {
return err
}
format := opts.format
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().ImagesFormat
} else {
format = formatter.TableFormatKey
@ -87,10 +86,10 @@ func runImages(dockerCli command.Cli, opts imagesOptions) error {
imageCtx := formatter.ImageContext{
Context: formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewImageFormat(format, opts.quiet, opts.showDigests),
Trunc: !opts.noTrunc,
Format: formatter.NewImageFormat(format, options.quiet, options.showDigests),
Trunc: !options.noTrunc,
},
Digest: opts.showDigests,
Digest: options.showDigests,
}
return formatter.ImageWrite(imageCtx, images)
}

View File

@ -7,7 +7,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/opts"
"github.com/docker/cli/opts"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
)
@ -20,14 +20,14 @@ type pruneOptions struct {
// NewPruneCommand returns a new cobra prune command for images
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
opts := pruneOptions{filter: opts.NewFilterOpt()}
options := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "prune [OPTIONS]",
Short: "Remove unused images",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
spaceReclaimed, output, err := runPrune(dockerCli, opts)
spaceReclaimed, output, err := runPrune(dockerCli, options)
if err != nil {
return err
}
@ -41,9 +41,9 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
}
flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images, not just dangling ones")
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones")
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
return cmd
}
@ -55,16 +55,16 @@ Are you sure you want to continue?`
Are you sure you want to continue?`
)
func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := opts.filter.Value()
pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all))
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := options.filter.Value()
pruneFilters.Add("dangling", fmt.Sprintf("%v", !options.all))
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
warning := danglingWarning
if opts.all {
if options.all {
warning = allImageWarning
}
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return
}

View File

@ -1,13 +1,12 @@
package network
import (
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type connectOptions struct {
@ -21,7 +20,7 @@ type connectOptions struct {
}
func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := connectOptions{
options := connectOptions{
links: opts.NewListOpts(opts.ValidateLink),
}
@ -30,34 +29,34 @@ func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Connect a container to a network",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
opts.network = args[0]
opts.container = args[1]
return runConnect(dockerCli, opts)
options.network = args[0]
options.container = args[1]
return runConnect(dockerCli, options)
},
}
flags := cmd.Flags()
flags.StringVar(&opts.ipaddress, "ip", "", "IPv4 address (e.g., 172.30.100.104)")
flags.StringVar(&opts.ipv6address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)")
flags.Var(&opts.links, "link", "Add link to another container")
flags.StringSliceVar(&opts.aliases, "alias", []string{}, "Add network-scoped alias for the container")
flags.StringSliceVar(&opts.linklocalips, "link-local-ip", []string{}, "Add a link-local address for the container")
flags.StringVar(&options.ipaddress, "ip", "", "IPv4 address (e.g., 172.30.100.104)")
flags.StringVar(&options.ipv6address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)")
flags.Var(&options.links, "link", "Add link to another container")
flags.StringSliceVar(&options.aliases, "alias", []string{}, "Add network-scoped alias for the container")
flags.StringSliceVar(&options.linklocalips, "link-local-ip", []string{}, "Add a link-local address for the container")
return cmd
}
func runConnect(dockerCli *command.DockerCli, opts connectOptions) error {
func runConnect(dockerCli *command.DockerCli, options connectOptions) error {
client := dockerCli.Client()
epConfig := &network.EndpointSettings{
IPAMConfig: &network.EndpointIPAMConfig{
IPv4Address: opts.ipaddress,
IPv6Address: opts.ipv6address,
LinkLocalIPs: opts.linklocalips,
IPv4Address: options.ipaddress,
IPv6Address: options.ipv6address,
LinkLocalIPs: options.linklocalips,
},
Links: opts.links.GetAll(),
Aliases: opts.aliases,
Links: options.links.GetAll(),
Aliases: options.aliases,
}
return client.NetworkConnect(context.Background(), opts.network, opts.container, epConfig)
return client.NetworkConnect(context.Background(), options.network, options.container, epConfig)
}

View File

@ -5,16 +5,15 @@ import (
"net"
"strings"
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/opts"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type createOptions struct {
@ -36,7 +35,7 @@ type createOptions struct {
}
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := createOptions{
options := createOptions{
driverOpts: *opts.NewMapOpts(nil, nil),
labels: opts.NewListOpts(opts.ValidateEnv),
ipamAux: *opts.NewMapOpts(nil, nil),
@ -48,59 +47,59 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "Create a network",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.name = args[0]
return runCreate(dockerCli, opts)
options.name = args[0]
return runCreate(dockerCli, options)
},
}
flags := cmd.Flags()
flags.StringVarP(&opts.driver, "driver", "d", "bridge", "Driver to manage the Network")
flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options")
flags.Var(&opts.labels, "label", "Set metadata on a network")
flags.BoolVar(&opts.internal, "internal", false, "Restrict external access to the network")
flags.BoolVar(&opts.ipv6, "ipv6", false, "Enable IPv6 networking")
flags.BoolVar(&opts.attachable, "attachable", false, "Enable manual container attachment")
flags.StringVarP(&options.driver, "driver", "d", "bridge", "Driver to manage the Network")
flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options")
flags.Var(&options.labels, "label", "Set metadata on a network")
flags.BoolVar(&options.internal, "internal", false, "Restrict external access to the network")
flags.BoolVar(&options.ipv6, "ipv6", false, "Enable IPv6 networking")
flags.BoolVar(&options.attachable, "attachable", false, "Enable manual container attachment")
flags.SetAnnotation("attachable", "version", []string{"1.25"})
flags.BoolVar(&opts.ingress, "ingress", false, "Create swarm routing-mesh network")
flags.BoolVar(&options.ingress, "ingress", false, "Create swarm routing-mesh network")
flags.SetAnnotation("ingress", "version", []string{"1.29"})
flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
flags.StringSliceVar(&opts.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment")
flags.StringSliceVar(&opts.ipamIPRange, "ip-range", []string{}, "Allocate container ip from a sub-range")
flags.StringSliceVar(&opts.ipamGateway, "gateway", []string{}, "IPv4 or IPv6 Gateway for the master subnet")
flags.StringVar(&options.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
flags.StringSliceVar(&options.ipamSubnet, "subnet", []string{}, "Subnet in CIDR format that represents a network segment")
flags.StringSliceVar(&options.ipamIPRange, "ip-range", []string{}, "Allocate container ip from a sub-range")
flags.StringSliceVar(&options.ipamGateway, "gateway", []string{}, "IPv4 or IPv6 Gateway for the master subnet")
flags.Var(&opts.ipamAux, "aux-address", "Auxiliary IPv4 or IPv6 addresses used by Network driver")
flags.Var(&opts.ipamOpt, "ipam-opt", "Set IPAM driver specific options")
flags.Var(&options.ipamAux, "aux-address", "Auxiliary IPv4 or IPv6 addresses used by Network driver")
flags.Var(&options.ipamOpt, "ipam-opt", "Set IPAM driver specific options")
return cmd
}
func runCreate(dockerCli *command.DockerCli, opts createOptions) error {
func runCreate(dockerCli *command.DockerCli, options createOptions) error {
client := dockerCli.Client()
ipamCfg, err := consolidateIpam(opts.ipamSubnet, opts.ipamIPRange, opts.ipamGateway, opts.ipamAux.GetAll())
ipamCfg, err := consolidateIpam(options.ipamSubnet, options.ipamIPRange, options.ipamGateway, options.ipamAux.GetAll())
if err != nil {
return err
}
// Construct network create request body
nc := types.NetworkCreate{
Driver: opts.driver,
Options: opts.driverOpts.GetAll(),
Driver: options.driver,
Options: options.driverOpts.GetAll(),
IPAM: &network.IPAM{
Driver: opts.ipamDriver,
Driver: options.ipamDriver,
Config: ipamCfg,
Options: opts.ipamOpt.GetAll(),
Options: options.ipamOpt.GetAll(),
},
CheckDuplicate: true,
Internal: opts.internal,
EnableIPv6: opts.ipv6,
Attachable: opts.attachable,
Ingress: opts.ingress,
Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
Internal: options.internal,
EnableIPv6: options.ipv6,
Attachable: options.attachable,
Ingress: options.ingress,
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
}
resp, err := client.NetworkCreate(context.Background(), opts.name, nc)
resp, err := client.NetworkCreate(context.Background(), options.name, nc)
if err != nil {
return err
}

View File

@ -3,14 +3,13 @@ package network
import (
"sort"
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type byNetworkName []types.NetworkResource
@ -27,7 +26,7 @@ type listOptions struct {
}
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := listOptions{filter: opts.NewFilterOpt()}
options := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ls [OPTIONS]",
@ -35,30 +34,30 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "List networks",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runList(dockerCli, opts)
return runList(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display network IDs")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output")
flags.StringVar(&opts.format, "format", "", "Pretty-print networks using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Provide filter values (e.g. 'driver=bridge')")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display network IDs")
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate the output")
flags.StringVar(&options.format, "format", "", "Pretty-print networks using a Go template")
flags.VarP(&options.filter, "filter", "f", "Provide filter values (e.g. 'driver=bridge')")
return cmd
}
func runList(dockerCli *command.DockerCli, opts listOptions) error {
func runList(dockerCli *command.DockerCli, options listOptions) error {
client := dockerCli.Client()
options := types.NetworkListOptions{Filters: opts.filter.Value()}
networkResources, err := client.NetworkList(context.Background(), options)
listOptions := types.NetworkListOptions{Filters: options.filter.Value()}
networkResources, err := client.NetworkList(context.Background(), listOptions)
if err != nil {
return err
}
format := opts.format
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().NetworksFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().NetworksFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().NetworksFormat
} else {
format = formatter.TableFormatKey
@ -69,8 +68,8 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
networksCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewNetworkFormat(format, opts.quiet),
Trunc: !opts.noTrunc,
Format: formatter.NewNetworkFormat(format, options.quiet),
Trunc: !options.noTrunc,
}
return formatter.NetworkWrite(networksCtx, networkResources)
}

View File

@ -3,12 +3,11 @@ package network
import (
"fmt"
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/opts"
"github.com/docker/cli/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type pruneOptions struct {
@ -18,14 +17,14 @@ type pruneOptions struct {
// NewPruneCommand returns a new cobra prune command for networks
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
opts := pruneOptions{filter: opts.NewFilterOpt()}
options := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "prune [OPTIONS]",
Short: "Remove all unused networks",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
output, err := runPrune(dockerCli, opts)
output, err := runPrune(dockerCli, options)
if err != nil {
return err
}
@ -38,8 +37,8 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
}
flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
return cmd
}
@ -47,10 +46,10 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
const warning = `WARNING! This will remove all networks not used by at least one container.
Are you sure you want to continue?`
func runPrune(dockerCli command.Cli, opts pruneOptions) (output string, err error) {
pruneFilters := command.PruneFilters(dockerCli, opts.filter.Value())
func runPrune(dockerCli command.Cli, options pruneOptions) (output string, err error) {
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return
}

View File

@ -1,14 +1,13 @@
package node
import (
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type listOptions struct {
@ -18,7 +17,7 @@ type listOptions struct {
}
func newListCommand(dockerCli command.Cli) *cobra.Command {
opts := listOptions{filter: opts.NewFilterOpt()}
options := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ls [OPTIONS]",
@ -26,30 +25,30 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
Short: "List nodes in the swarm",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runList(dockerCli, opts)
return runList(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVar(&opts.format, "format", "", "Pretty-print nodes using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVar(&options.format, "format", "", "Pretty-print nodes using a Go template")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
return cmd
}
func runList(dockerCli command.Cli, opts listOptions) error {
func runList(dockerCli command.Cli, options listOptions) error {
client := dockerCli.Client()
ctx := context.Background()
nodes, err := client.NodeList(
ctx,
types.NodeListOptions{Filters: opts.filter.Value()})
types.NodeListOptions{Filters: options.filter.Value()})
if err != nil {
return err
}
info := types.Info{}
if len(nodes) > 0 && !opts.quiet {
if len(nodes) > 0 && !options.quiet {
// only non-empty nodes and not quiet, should we call /info api
info, err = client.Info(ctx)
if err != nil {
@ -57,17 +56,17 @@ func runList(dockerCli command.Cli, opts listOptions) error {
}
}
format := opts.format
format := options.format
if len(format) == 0 {
format = formatter.TableFormatKey
if len(dockerCli.ConfigFile().NodesFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().NodesFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().NodesFormat
}
}
nodesCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewNodeFormat(format, opts.quiet),
Format: formatter.NewNodeFormat(format, options.quiet),
}
return formatter.NodeWrite(nodesCtx, nodes, info)
}

View File

@ -1,7 +1,7 @@
package node
import (
"github.com/docker/docker/opts"
"github.com/docker/cli/opts"
)
type nodeOptions struct {

View File

@ -8,9 +8,9 @@ import (
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/task"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -26,33 +26,33 @@ type psOptions struct {
}
func newPsCommand(dockerCli command.Cli) *cobra.Command {
opts := psOptions{filter: opts.NewFilterOpt()}
options := psOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ps [OPTIONS] [NODE...]",
Short: "List tasks running on one or more nodes, defaults to current node",
Args: cli.RequiresMinArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
opts.nodeIDs = []string{"self"}
options.nodeIDs = []string{"self"}
if len(args) != 0 {
opts.nodeIDs = args
options.nodeIDs = args
}
return runPs(dockerCli, opts)
return runPs(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.StringVar(&opts.format, "format", "", "Pretty-print tasks using a Go template")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display task IDs")
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output")
flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
return cmd
}
func runPs(dockerCli command.Cli, opts psOptions) error {
func runPs(dockerCli command.Cli, options psOptions) error {
client := dockerCli.Client()
ctx := context.Background()
@ -61,7 +61,7 @@ func runPs(dockerCli command.Cli, opts psOptions) error {
tasks []swarm.Task
)
for _, nodeID := range opts.nodeIDs {
for _, nodeID := range options.nodeIDs {
nodeRef, err := Reference(ctx, client, nodeID)
if err != nil {
errs = append(errs, err.Error())
@ -74,7 +74,7 @@ func runPs(dockerCli command.Cli, opts psOptions) error {
continue
}
filter := opts.filter.Value()
filter := options.filter.Value()
filter.Add("node", node.ID)
nodeTasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
@ -86,9 +86,9 @@ func runPs(dockerCli command.Cli, opts psOptions) error {
tasks = append(tasks, nodeTasks...)
}
format := opts.format
format := options.format
if len(format) == 0 {
if dockerCli.ConfigFile() != nil && len(dockerCli.ConfigFile().TasksFormat) > 0 && !opts.quiet {
if dockerCli.ConfigFile() != nil && len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().TasksFormat
} else {
format = formatter.TableFormatKey
@ -96,7 +96,7 @@ func runPs(dockerCli command.Cli, opts psOptions) error {
}
if len(errs) == 0 || len(tasks) != 0 {
if err := task.Print(ctx, dockerCli, tasks, idresolver.New(client, opts.noResolve), !opts.noTrunc, opts.quiet, format); err != nil {
if err := task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format); err != nil {
errs = append(errs, err.Error())
}
}

View File

@ -5,8 +5,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/opts"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -19,7 +19,7 @@ var (
)
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
nodeOpts := newNodeOptions()
options := newNodeOptions()
cmd := &cobra.Command{
Use: "update [OPTIONS] NODE",
@ -31,9 +31,9 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
}
flags := cmd.Flags()
flags.StringVar(&nodeOpts.role, flagRole, "", `Role of the node ("worker"|"manager")`)
flags.StringVar(&nodeOpts.availability, flagAvailability, "", `Availability of the node ("active"|"pause"|"drain")`)
flags.Var(&nodeOpts.annotations.labels, flagLabelAdd, "Add or update a node label (key=value)")
flags.StringVar(&options.role, flagRole, "", `Role of the node ("worker"|"manager")`)
flags.StringVar(&options.availability, flagAvailability, "", `Availability of the node ("active"|"pause"|"drain")`)
flags.Var(&options.annotations.labels, flagLabelAdd, "Add or update a node label (key=value)")
labelKeys := opts.NewListOpts(nil)
flags.Var(&labelKeys, flagLabelRemove, "Remove a node label if exists")
return cmd

View File

@ -4,7 +4,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/opts"
"github.com/docker/cli/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
@ -17,7 +17,7 @@ type listOptions struct {
}
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := listOptions{filter: opts.NewFilterOpt()}
options := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ls [OPTIONS]",
@ -25,29 +25,29 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
Aliases: []string{"list"},
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runList(dockerCli, opts)
return runList(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display plugin IDs")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
flags.StringVar(&opts.format, "format", "", "Pretty-print plugins using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Provide filter values (e.g. 'enabled=true')")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display plugin IDs")
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
flags.StringVar(&options.format, "format", "", "Pretty-print plugins using a Go template")
flags.VarP(&options.filter, "filter", "f", "Provide filter values (e.g. 'enabled=true')")
return cmd
}
func runList(dockerCli *command.DockerCli, opts listOptions) error {
plugins, err := dockerCli.Client().PluginList(context.Background(), opts.filter.Value())
func runList(dockerCli *command.DockerCli, options listOptions) error {
plugins, err := dockerCli.Client().PluginList(context.Background(), options.filter.Value())
if err != nil {
return err
}
format := opts.format
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().PluginsFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().PluginsFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().PluginsFormat
} else {
format = formatter.TableFormatKey
@ -56,8 +56,8 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
pluginsCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewPluginFormat(format, opts.quiet),
Trunc: !opts.noTrunc,
Format: formatter.NewPluginFormat(format, options.quiet),
Trunc: !options.noTrunc,
}
return formatter.PluginWrite(pluginsCtx, plugins)
}

View File

@ -6,7 +6,7 @@ import (
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/command/network"
"github.com/docker/cli/cli/command/volume"
"github.com/docker/docker/opts"
"github.com/docker/cli/opts"
"github.com/spf13/cobra"
)

View File

@ -6,16 +6,15 @@ import (
"strings"
"text/tabwriter"
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/registry"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type searchOptions struct {
@ -31,26 +30,26 @@ type searchOptions struct {
// NewSearchCommand creates a new `docker search` command
func NewSearchCommand(dockerCli command.Cli) *cobra.Command {
opts := searchOptions{filter: opts.NewFilterOpt()}
options := searchOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "search [OPTIONS] TERM",
Short: "Search the Docker Hub for images",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.term = args[0]
return runSearch(dockerCli, opts)
options.term = args[0]
return runSearch(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.IntVar(&opts.limit, "limit", registry.DefaultSearchLimit, "Max number of search results")
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
flags.IntVar(&options.limit, "limit", registry.DefaultSearchLimit, "Max number of search results")
flags.BoolVar(&opts.automated, "automated", false, "Only show automated builds")
flags.UintVarP(&opts.stars, "stars", "s", 0, "Only displays with at least x stars")
flags.BoolVar(&options.automated, "automated", false, "Only show automated builds")
flags.UintVarP(&options.stars, "stars", "s", 0, "Only displays with at least x stars")
flags.MarkDeprecated("automated", "use --filter=is-automated=true instead")
flags.MarkDeprecated("stars", "use --filter=stars=3 instead")
@ -58,8 +57,8 @@ func NewSearchCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runSearch(dockerCli command.Cli, opts searchOptions) error {
indexInfo, err := registry.ParseSearchIndexInfo(opts.term)
func runSearch(dockerCli command.Cli, options searchOptions) error {
indexInfo, err := registry.ParseSearchIndexInfo(options.term)
if err != nil {
return err
}
@ -74,16 +73,16 @@ func runSearch(dockerCli command.Cli, opts searchOptions) error {
return err
}
options := types.ImageSearchOptions{
searchOptions := types.ImageSearchOptions{
RegistryAuth: encodedAuth,
PrivilegeFunc: requestPrivilege,
Filters: opts.filter.Value(),
Limit: opts.limit,
Filters: options.filter.Value(),
Limit: options.limit,
}
clnt := dockerCli.Client()
unorderedResults, err := clnt.ImageSearch(ctx, opts.term, options)
unorderedResults, err := clnt.ImageSearch(ctx, options.term, searchOptions)
if err != nil {
return err
}
@ -95,12 +94,12 @@ func runSearch(dockerCli command.Cli, opts searchOptions) error {
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
for _, res := range results {
// --automated and -s, --stars are deprecated since Docker 1.12
if (opts.automated && !res.IsAutomated) || (int(opts.stars) > res.StarCount) {
if (options.automated && !res.IsAutomated) || (int(options.stars) > res.StarCount) {
continue
}
desc := strings.Replace(res.Description, "\n", " ", -1)
desc = strings.Replace(desc, "\r", " ", -1)
if !opts.noTrunc {
if !options.noTrunc {
desc = stringutils.Ellipsis(desc, 45)
}
fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount)

View File

@ -7,8 +7,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/system"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
@ -23,7 +23,7 @@ type createOptions struct {
}
func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command {
createOpts := createOptions{
options := createOptions{
labels: opts.NewListOpts(opts.ValidateEnv),
}
@ -32,13 +32,13 @@ func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command {
Short: "Create a secret from a file or STDIN as content",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
createOpts.name = args[0]
createOpts.file = args[1]
return runSecretCreate(dockerCli, createOpts)
options.name = args[0]
options.file = args[1]
return runSecretCreate(dockerCli, options)
},
}
flags := cmd.Flags()
flags.VarP(&createOpts.labels, "label", "l", "Secret labels")
flags.VarP(&options.labels, "label", "l", "Secret labels")
return cmd
}

View File

@ -4,8 +4,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
@ -17,7 +17,7 @@ type listOptions struct {
}
func newSecretListCommand(dockerCli command.Cli) *cobra.Command {
opts := listOptions{filter: opts.NewFilterOpt()}
options := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ls [OPTIONS]",
@ -25,29 +25,29 @@ func newSecretListCommand(dockerCli command.Cli) *cobra.Command {
Short: "List secrets",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runSecretList(dockerCli, opts)
return runSecretList(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVarP(&opts.format, "format", "", "", "Pretty-print secrets using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVarP(&options.format, "format", "", "", "Pretty-print secrets using a Go template")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
return cmd
}
func runSecretList(dockerCli command.Cli, opts listOptions) error {
func runSecretList(dockerCli command.Cli, options listOptions) error {
client := dockerCli.Client()
ctx := context.Background()
secrets, err := client.SecretList(ctx, types.SecretListOptions{Filters: opts.filter.Value()})
secrets, err := client.SecretList(ctx, types.SecretListOptions{Filters: options.filter.Value()})
if err != nil {
return err
}
format := opts.format
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().SecretFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().SecretFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().SecretFormat
} else {
format = formatter.TableFormatKey
@ -55,7 +55,7 @@ func runSecretList(dockerCli command.Cli, opts listOptions) error {
}
secretCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewSecretFormat(format, opts.quiet),
Format: formatter.NewSecretFormat(format, options.quiet),
}
return formatter.SecretWrite(secretCtx, secrets)
}

View File

@ -6,10 +6,10 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
@ -21,7 +21,7 @@ type listOptions struct {
}
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := listOptions{filter: opts.NewFilterOpt()}
options := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ls [OPTIONS]",
@ -29,30 +29,30 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
Short: "List services",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runList(dockerCli, opts)
return runList(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVar(&opts.format, "format", "", "Pretty-print services using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVar(&options.format, "format", "", "Pretty-print services using a Go template")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
return cmd
}
func runList(dockerCli *command.DockerCli, opts listOptions) error {
func runList(dockerCli *command.DockerCli, options listOptions) error {
ctx := context.Background()
client := dockerCli.Client()
serviceFilters := opts.filter.Value()
serviceFilters := options.filter.Value()
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceFilters})
if err != nil {
return err
}
info := map[string]formatter.ServiceListInfo{}
if len(services) > 0 && !opts.quiet {
if len(services) > 0 && !options.quiet {
// only non-empty services and not quiet, should we call TaskList and NodeList api
taskFilter := filters.NewArgs()
for _, service := range services {
@ -72,9 +72,9 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
info = GetServicesStatus(services, nodes, tasks)
}
format := opts.format
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().ServicesFormat
} else {
format = formatter.TableFormatKey
@ -83,7 +83,7 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
servicesCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewServiceListFormat(format, opts.quiet),
Format: formatter.NewServiceListFormat(format, options.quiet),
}
return formatter.ServiceListWrite(servicesCtx, services, info)
}

View File

@ -7,10 +7,10 @@ import (
"strings"
"time"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/opts"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/swarmkit/api"
"github.com/docker/swarmkit/api/defaults"

View File

@ -4,8 +4,8 @@ import (
"testing"
"time"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/opts"
"github.com/stretchr/testify/assert"
)

View File

@ -3,19 +3,18 @@ package service
import (
"strings"
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/node"
"github.com/docker/cli/cli/command/task"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type psOptions struct {
@ -28,36 +27,36 @@ type psOptions struct {
}
func newPsCommand(dockerCli command.Cli) *cobra.Command {
opts := psOptions{filter: opts.NewFilterOpt()}
options := psOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ps [OPTIONS] SERVICE [SERVICE...]",
Short: "List the tasks of one or more services",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.services = args
return runPS(dockerCli, opts)
options.services = args
return runPS(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display task IDs")
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
flags.StringVar(&opts.format, "format", "", "Pretty-print tasks using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output")
flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names")
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
return cmd
}
func runPS(dockerCli command.Cli, opts psOptions) error {
func runPS(dockerCli command.Cli, options psOptions) error {
client := dockerCli.Client()
ctx := context.Background()
filter := opts.filter.Value()
filter := options.filter.Value()
serviceIDFilter := filters.NewArgs()
serviceNameFilter := filters.NewArgs()
for _, service := range opts.services {
for _, service := range options.services {
serviceIDFilter.Add("id", service)
serviceNameFilter.Add("name", service)
}
@ -70,7 +69,7 @@ func runPS(dockerCli command.Cli, opts psOptions) error {
return err
}
for _, service := range opts.services {
for _, service := range options.services {
serviceCount := 0
// Lookup by ID/Prefix
for _, serviceEntry := range serviceByIDList {
@ -110,14 +109,14 @@ func runPS(dockerCli command.Cli, opts psOptions) error {
return err
}
format := opts.format
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().TasksFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().TasksFormat
} else {
format = formatter.TableFormatKey
}
}
return task.Print(ctx, dockerCli, tasks, idresolver.New(client, opts.noResolve), !opts.noTrunc, opts.quiet, format)
return task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format)
}

View File

@ -8,13 +8,13 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/docker/docker/opts"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/swarmkit/api/defaults"
"github.com/pkg/errors"
@ -24,14 +24,14 @@ import (
)
func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
serviceOpts := newServiceOptions()
options := newServiceOptions()
cmd := &cobra.Command{
Use: "update [OPTIONS] SERVICE",
Short: "Update a service",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return runUpdate(dockerCli, cmd.Flags(), serviceOpts, args[0])
return runUpdate(dockerCli, cmd.Flags(), options, args[0])
},
}
@ -42,7 +42,7 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
flags.SetAnnotation("rollback", "version", []string{"1.25"})
flags.Bool("force", false, "Force update even if no changes require it")
flags.SetAnnotation("force", "version", []string{"1.25"})
addServiceFlags(flags, serviceOpts, nil)
addServiceFlags(flags, options, nil)
flags.Var(newListOptsVar(), flagEnvRemove, "Remove an environment variable")
flags.Var(newListOptsVar(), flagGroupRemove, "Remove a previously added supplementary user group from the container")
@ -61,39 +61,39 @@ func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
flags.SetAnnotation(flagDNSSearchRemove, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagHostRemove, "Remove a custom host-to-IP mapping (host:ip)")
flags.SetAnnotation(flagHostRemove, "version", []string{"1.25"})
flags.Var(&serviceOpts.labels, flagLabelAdd, "Add or update a service label")
flags.Var(&serviceOpts.containerLabels, flagContainerLabelAdd, "Add or update a container label")
flags.Var(&serviceOpts.env, flagEnvAdd, "Add or update an environment variable")
flags.Var(&options.labels, flagLabelAdd, "Add or update a service label")
flags.Var(&options.containerLabels, flagContainerLabelAdd, "Add or update a container label")
flags.Var(&options.env, flagEnvAdd, "Add or update an environment variable")
flags.Var(newListOptsVar(), flagSecretRemove, "Remove a secret")
flags.SetAnnotation(flagSecretRemove, "version", []string{"1.25"})
flags.Var(&serviceOpts.secrets, flagSecretAdd, "Add or update a secret on a service")
flags.Var(&options.secrets, flagSecretAdd, "Add or update a secret on a service")
flags.SetAnnotation(flagSecretAdd, "version", []string{"1.25"})
flags.Var(newListOptsVar(), flagConfigRemove, "Remove a configuration file")
flags.SetAnnotation(flagConfigRemove, "version", []string{"1.30"})
flags.Var(&serviceOpts.configs, flagConfigAdd, "Add or update a config file on a service")
flags.Var(&options.configs, flagConfigAdd, "Add or update a config file on a service")
flags.SetAnnotation(flagConfigAdd, "version", []string{"1.30"})
flags.Var(&serviceOpts.mounts, flagMountAdd, "Add or update a mount on a service")
flags.Var(&serviceOpts.constraints, flagConstraintAdd, "Add or update a placement constraint")
flags.Var(&serviceOpts.placementPrefs, flagPlacementPrefAdd, "Add a placement preference")
flags.Var(&options.mounts, flagMountAdd, "Add or update a mount on a service")
flags.Var(&options.constraints, flagConstraintAdd, "Add or update a placement constraint")
flags.Var(&options.placementPrefs, flagPlacementPrefAdd, "Add a placement preference")
flags.SetAnnotation(flagPlacementPrefAdd, "version", []string{"1.28"})
flags.Var(&placementPrefOpts{}, flagPlacementPrefRemove, "Remove a placement preference")
flags.SetAnnotation(flagPlacementPrefRemove, "version", []string{"1.28"})
flags.Var(&serviceOpts.networks, flagNetworkAdd, "Add a network")
flags.Var(&options.networks, flagNetworkAdd, "Add a network")
flags.SetAnnotation(flagNetworkAdd, "version", []string{"1.29"})
flags.Var(newListOptsVar(), flagNetworkRemove, "Remove a network")
flags.SetAnnotation(flagNetworkRemove, "version", []string{"1.29"})
flags.Var(&serviceOpts.endpoint.publishPorts, flagPublishAdd, "Add or update a published port")
flags.Var(&serviceOpts.groups, flagGroupAdd, "Add an additional supplementary user group to the container")
flags.Var(&options.endpoint.publishPorts, flagPublishAdd, "Add or update a published port")
flags.Var(&options.groups, flagGroupAdd, "Add an additional supplementary user group to the container")
flags.SetAnnotation(flagGroupAdd, "version", []string{"1.25"})
flags.Var(&serviceOpts.dns, flagDNSAdd, "Add or update a custom DNS server")
flags.Var(&options.dns, flagDNSAdd, "Add or update a custom DNS server")
flags.SetAnnotation(flagDNSAdd, "version", []string{"1.25"})
flags.Var(&serviceOpts.dnsOption, flagDNSOptionAdd, "Add or update a DNS option")
flags.Var(&options.dnsOption, flagDNSOptionAdd, "Add or update a DNS option")
flags.SetAnnotation(flagDNSOptionAdd, "version", []string{"1.25"})
flags.Var(&serviceOpts.dnsSearch, flagDNSSearchAdd, "Add or update a custom DNS search domain")
flags.Var(&options.dnsSearch, flagDNSSearchAdd, "Add or update a custom DNS search domain")
flags.SetAnnotation(flagDNSSearchAdd, "version", []string{"1.25"})
flags.Var(&serviceOpts.hosts, flagHostAdd, "Add or update a custom host-to-IP mapping (host:ip)")
flags.Var(&options.hosts, flagHostAdd, "Add or update a custom host-to-IP mapping (host:ip)")
flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"})
return cmd
@ -104,7 +104,7 @@ func newListOptsVar() *opts.ListOpts {
}
// nolint: gocyclo
func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *serviceOptions, serviceID string) error {
func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, options *serviceOptions, serviceID string) error {
apiClient := dockerCli.Client()
ctx := context.Background()
@ -214,7 +214,7 @@ func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *service
fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
if opts.detach {
if options.detach {
if !flags.Changed("detach") {
fmt.Fprintln(dockerCli.Err(), "Since --detach=false was not specified, tasks will be updated in the background.\n"+
"In a future release, --detach=false will become the default.")
@ -222,7 +222,7 @@ func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *service
return nil
}
return waitOnService(ctx, dockerCli, serviceID, opts)
return waitOnService(ctx, dockerCli, serviceID, options)
}
// nolint: gocyclo

View File

@ -1,14 +1,13 @@
package stack
import (
"golang.org/x/net/context"
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/opts"
"golang.org/x/net/context"
)
func getStackFilter(namespace string) filters.Args {

View File

@ -3,16 +3,15 @@ package stack
import (
"fmt"
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/task"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type psOptions struct {
@ -25,33 +24,33 @@ type psOptions struct {
}
func newPsCommand(dockerCli command.Cli) *cobra.Command {
opts := psOptions{filter: opts.NewFilterOpt()}
options := psOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ps [OPTIONS] STACK",
Short: "List the tasks in the stack",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.namespace = args[0]
return runPS(dockerCli, opts)
options.namespace = args[0]
return runPS(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display task IDs")
flags.StringVar(&opts.format, "format", "", "Pretty-print tasks using a Go template")
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output")
flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
return cmd
}
func runPS(dockerCli command.Cli, opts psOptions) error {
namespace := opts.namespace
func runPS(dockerCli command.Cli, options psOptions) error {
namespace := options.namespace
client := dockerCli.Client()
ctx := context.Background()
filter := getStackFilterFromOpt(opts.namespace, opts.filter)
filter := getStackFilterFromOpt(options.namespace, options.filter)
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
if err != nil {
@ -63,14 +62,14 @@ func runPS(dockerCli command.Cli, opts psOptions) error {
return nil
}
format := opts.format
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().TasksFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().TasksFormat
} else {
format = formatter.TableFormatKey
}
}
return task.Print(ctx, dockerCli, tasks, idresolver.New(client, opts.noResolve), !opts.noTrunc, opts.quiet, format)
return task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format)
}

View File

@ -3,16 +3,15 @@ package stack
import (
"fmt"
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/service"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type servicesOptions struct {
@ -23,30 +22,30 @@ type servicesOptions struct {
}
func newServicesCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := servicesOptions{filter: opts.NewFilterOpt()}
options := servicesOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "services [OPTIONS] STACK",
Short: "List the services in the stack",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.namespace = args[0]
return runServices(dockerCli, opts)
options.namespace = args[0]
return runServices(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVar(&opts.format, "format", "", "Pretty-print services using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVar(&options.format, "format", "", "Pretty-print services using a Go template")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
return cmd
}
func runServices(dockerCli *command.DockerCli, opts servicesOptions) error {
func runServices(dockerCli *command.DockerCli, options servicesOptions) error {
ctx := context.Background()
client := dockerCli.Client()
filter := getStackFilterFromOpt(opts.namespace, opts.filter)
filter := getStackFilterFromOpt(options.namespace, options.filter)
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: filter})
if err != nil {
return err
@ -56,12 +55,12 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error {
// if no services in this stack, print message and exit 0
if len(services) == 0 {
fmt.Fprintf(out, "Nothing found in stack: %s\n", opts.namespace)
fmt.Fprintf(out, "Nothing found in stack: %s\n", options.namespace)
return nil
}
info := map[string]formatter.ServiceListInfo{}
if !opts.quiet {
if !options.quiet {
taskFilter := filters.NewArgs()
for _, service := range services {
taskFilter.Add("service", service.ID)
@ -80,9 +79,9 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error {
info = service.GetServicesStatus(services, nodes, tasks)
}
format := opts.format
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().ServicesFormat
} else {
format = formatter.TableFormatKey
@ -91,7 +90,7 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error {
servicesCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewServiceListFormat(format, opts.quiet),
Format: formatter.NewServiceListFormat(format, options.quiet),
}
return formatter.ServiceListWrite(servicesCtx, services, info)
}

View File

@ -8,8 +8,8 @@ import (
"strings"
"time"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/opts"
"github.com/pkg/errors"
"github.com/spf13/pflag"
)

View File

@ -9,16 +9,15 @@ import (
"text/template"
"time"
"golang.org/x/net/context"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
eventtypes "github.com/docker/docker/api/types/events"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/jsonlog"
"github.com/docker/docker/pkg/templates"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type eventsOptions struct {
@ -30,41 +29,41 @@ type eventsOptions struct {
// NewEventsCommand creates a new cobra.Command for `docker events`
func NewEventsCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := eventsOptions{filter: opts.NewFilterOpt()}
options := eventsOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "events [OPTIONS]",
Short: "Get real time events from the server",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runEvents(dockerCli, &opts)
return runEvents(dockerCli, &options)
},
}
flags := cmd.Flags()
flags.StringVar(&opts.since, "since", "", "Show all events created since timestamp")
flags.StringVar(&opts.until, "until", "", "Stream events until this timestamp")
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
flags.StringVar(&opts.format, "format", "", "Format the output using the given Go template")
flags.StringVar(&options.since, "since", "", "Show all events created since timestamp")
flags.StringVar(&options.until, "until", "", "Stream events until this timestamp")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
flags.StringVar(&options.format, "format", "", "Format the output using the given Go template")
return cmd
}
func runEvents(dockerCli *command.DockerCli, opts *eventsOptions) error {
tmpl, err := makeTemplate(opts.format)
func runEvents(dockerCli *command.DockerCli, options *eventsOptions) error {
tmpl, err := makeTemplate(options.format)
if err != nil {
return cli.StatusError{
StatusCode: 64,
Status: "Error parsing format: " + err.Error()}
}
options := types.EventsOptions{
Since: opts.since,
Until: opts.until,
Filters: opts.filter.Value(),
eventOptions := types.EventsOptions{
Since: options.since,
Until: options.until,
Filters: options.filter.Value(),
}
ctx, cancel := context.WithCancel(context.Background())
events, errs := dockerCli.Client().Events(ctx, options)
events, errs := dockerCli.Client().Events(ctx, eventOptions)
defer cancel()
out := dockerCli.Out()

View File

@ -6,7 +6,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/prune"
"github.com/docker/docker/opts"
"github.com/docker/cli/opts"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
)
@ -19,22 +19,22 @@ type pruneOptions struct {
// NewPruneCommand creates a new cobra.Command for `docker prune`
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
opts := pruneOptions{filter: opts.NewFilterOpt()}
options := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "prune [OPTIONS]",
Short: "Remove unused data",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runPrune(dockerCli, opts)
return runPrune(dockerCli, options)
},
Tags: map[string]string{"version": "1.25"},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
flags.BoolVarP(&opts.all, "all", "a", false, "Remove all unused images not just dangling ones")
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images not just dangling ones")
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
return cmd
}

View File

@ -5,8 +5,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
volumetypes "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/opts"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -21,7 +21,7 @@ type createOptions struct {
}
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
opts := createOptions{
options := createOptions{
driverOpts: *opts.NewMapOpts(nil, nil),
labels: opts.NewListOpts(opts.ValidateEnv),
}
@ -32,32 +32,32 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
Args: cli.RequiresMaxArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 1 {
if opts.name != "" {
if options.name != "" {
return errors.Errorf("Conflicting options: either specify --name or provide positional arg, not both\n")
}
opts.name = args[0]
options.name = args[0]
}
return runCreate(dockerCli, opts)
return runCreate(dockerCli, options)
},
}
flags := cmd.Flags()
flags.StringVarP(&opts.driver, "driver", "d", "local", "Specify volume driver name")
flags.StringVar(&opts.name, "name", "", "Specify volume name")
flags.StringVarP(&options.driver, "driver", "d", "local", "Specify volume driver name")
flags.StringVar(&options.name, "name", "", "Specify volume name")
flags.Lookup("name").Hidden = true
flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options")
flags.Var(&opts.labels, "label", "Set metadata for a volume")
flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options")
flags.Var(&options.labels, "label", "Set metadata for a volume")
return cmd
}
func runCreate(dockerCli command.Cli, opts createOptions) error {
func runCreate(dockerCli command.Cli, options createOptions) error {
client := dockerCli.Client()
volReq := volumetypes.VolumesCreateBody{
Driver: opts.driver,
DriverOpts: opts.driverOpts.GetAll(),
Name: opts.name,
Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
Driver: options.driver,
DriverOpts: options.driverOpts.GetAll(),
Name: options.name,
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
}
vol, err := client.VolumeCreate(context.Background(), volReq)

View File

@ -6,8 +6,8 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/opts"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
@ -27,7 +27,7 @@ type listOptions struct {
}
func newListCommand(dockerCli command.Cli) *cobra.Command {
opts := listOptions{filter: opts.NewFilterOpt()}
options := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ls [OPTIONS]",
@ -35,28 +35,28 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
Short: "List volumes",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
return runList(dockerCli, opts)
return runList(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display volume names")
flags.StringVar(&opts.format, "format", "", "Pretty-print volumes using a Go template")
flags.VarP(&opts.filter, "filter", "f", "Provide filter values (e.g. 'dangling=true')")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display volume names")
flags.StringVar(&options.format, "format", "", "Pretty-print volumes using a Go template")
flags.VarP(&options.filter, "filter", "f", "Provide filter values (e.g. 'dangling=true')")
return cmd
}
func runList(dockerCli command.Cli, opts listOptions) error {
func runList(dockerCli command.Cli, options listOptions) error {
client := dockerCli.Client()
volumes, err := client.VolumeList(context.Background(), opts.filter.Value())
volumes, err := client.VolumeList(context.Background(), options.filter.Value())
if err != nil {
return err
}
format := opts.format
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().VolumesFormat) > 0 && !opts.quiet {
if len(dockerCli.ConfigFile().VolumesFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().VolumesFormat
} else {
format = formatter.TableFormatKey
@ -67,7 +67,7 @@ func runList(dockerCli command.Cli, opts listOptions) error {
volumeCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewVolumeFormat(format, opts.quiet),
Format: formatter.NewVolumeFormat(format, options.quiet),
}
return formatter.VolumeWrite(volumeCtx, volumes.Volumes)
}

View File

@ -5,7 +5,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/opts"
"github.com/docker/cli/opts"
units "github.com/docker/go-units"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -18,14 +18,14 @@ type pruneOptions struct {
// NewPruneCommand returns a new cobra prune command for volumes
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
opts := pruneOptions{filter: opts.NewFilterOpt()}
options := pruneOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "prune [OPTIONS]",
Short: "Remove all unused volumes",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
spaceReclaimed, output, err := runPrune(dockerCli, opts)
spaceReclaimed, output, err := runPrune(dockerCli, options)
if err != nil {
return err
}
@ -39,8 +39,8 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
}
flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'label=<label>')")
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'label=<label>')")
return cmd
}
@ -48,10 +48,10 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
const warning = `WARNING! This will remove all volumes not used by at least one container.
Are you sure you want to continue?`
func runPrune(dockerCli command.Cli, opts pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := command.PruneFilters(dockerCli, opts.filter.Value())
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
return
}

View File

@ -9,11 +9,11 @@ import (
servicecli "github.com/docker/cli/cli/command/service"
composetypes "github.com/docker/cli/cli/compose/types"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/docker/docker/opts"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
)

View File

@ -12,7 +12,7 @@ import (
"github.com/docker/cli/cli/compose/schema"
"github.com/docker/cli/cli/compose/template"
"github.com/docker/cli/cli/compose/types"
"github.com/docker/docker/opts"
"github.com/docker/cli/opts"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/go-connections/nat"
units "github.com/docker/go-units"

View File

@ -7,7 +7,7 @@ import (
"github.com/Sirupsen/logrus"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/docker/opts"
"github.com/docker/cli/opts"
"github.com/docker/go-connections/tlsconfig"
"github.com/spf13/pflag"
)

98
opts/config.go Normal file
View File

@ -0,0 +1,98 @@
package opts
import (
"encoding/csv"
"fmt"
"os"
"strconv"
"strings"
swarmtypes "github.com/docker/docker/api/types/swarm"
)
// ConfigOpt is a Value type for parsing configs
type ConfigOpt struct {
values []*swarmtypes.ConfigReference
}
// Set a new config value
func (o *ConfigOpt) Set(value string) error {
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return err
}
options := &swarmtypes.ConfigReference{
File: &swarmtypes.ConfigReferenceFileTarget{
UID: "0",
GID: "0",
Mode: 0444,
},
}
// support a simple syntax of --config foo
if len(fields) == 1 {
options.File.Name = fields[0]
options.ConfigName = fields[0]
o.values = append(o.values, options)
return nil
}
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
key := strings.ToLower(parts[0])
if len(parts) != 2 {
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
}
value := parts[1]
switch key {
case "source", "src":
options.ConfigName = value
case "target":
options.File.Name = value
case "uid":
options.File.UID = value
case "gid":
options.File.GID = value
case "mode":
m, err := strconv.ParseUint(value, 0, 32)
if err != nil {
return fmt.Errorf("invalid mode specified: %v", err)
}
options.File.Mode = os.FileMode(m)
default:
return fmt.Errorf("invalid field in config request: %s", key)
}
}
if options.ConfigName == "" {
return fmt.Errorf("source is required")
}
o.values = append(o.values, options)
return nil
}
// Type returns the type of this option
func (o *ConfigOpt) Type() string {
return "config"
}
// String returns a string repr of this option
func (o *ConfigOpt) String() string {
configs := []string{}
for _, config := range o.values {
repr := fmt.Sprintf("%s -> %s", config.ConfigName, config.File.Name)
configs = append(configs, repr)
}
return strings.Join(configs, ", ")
}
// Value returns the config requests
func (o *ConfigOpt) Value() []*swarmtypes.ConfigReference {
return o.values
}

46
opts/env.go Normal file
View File

@ -0,0 +1,46 @@
package opts
import (
"fmt"
"os"
"runtime"
"strings"
)
// ValidateEnv validates an environment variable and returns it.
// If no value is specified, it returns the current value using os.Getenv.
//
// As on ParseEnvFile and related to #16585, environment variable names
// are not validate what so ever, it's up to application inside docker
// to validate them or not.
//
// The only validation here is to check if name is empty, per #25099
func ValidateEnv(val string) (string, error) {
arr := strings.Split(val, "=")
if arr[0] == "" {
return "", fmt.Errorf("invalid environment variable: %s", val)
}
if len(arr) > 1 {
return val, nil
}
if !doesEnvExist(val) {
return val, nil
}
return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
}
func doesEnvExist(name string) bool {
for _, entry := range os.Environ() {
parts := strings.SplitN(entry, "=", 2)
if runtime.GOOS == "windows" {
// Environment variable are case-insensitive on Windows. PaTh, path and PATH are equivalent.
if strings.EqualFold(parts[0], name) {
return true
}
}
if parts[0] == name {
return true
}
}
return false
}

42
opts/env_test.go Normal file
View File

@ -0,0 +1,42 @@
package opts
import (
"fmt"
"os"
"runtime"
"testing"
)
func TestValidateEnv(t *testing.T) {
valids := map[string]string{
"a": "a",
"something": "something",
"_=a": "_=a",
"env1=value1": "env1=value1",
"_env1=value1": "_env1=value1",
"env2=value2=value3": "env2=value2=value3",
"env3=abc!qwe": "env3=abc!qwe",
"env_4=value 4": "env_4=value 4",
"PATH": fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
"PATH=something": "PATH=something",
"asd!qwe": "asd!qwe",
"1asd": "1asd",
"123": "123",
"some space": "some space",
" some space before": " some space before",
"some space after ": "some space after ",
}
// Environment variables are case in-sensitive on Windows
if runtime.GOOS == "windows" {
valids["PaTh"] = fmt.Sprintf("PaTh=%v", os.Getenv("PATH"))
}
for value, expected := range valids {
actual, err := ValidateEnv(value)
if err != nil {
t.Fatal(err)
}
if actual != expected {
t.Fatalf("Expected [%v], got [%v]", expected, actual)
}
}
}

165
opts/hosts.go Normal file
View File

@ -0,0 +1,165 @@
package opts
import (
"fmt"
"net"
"net/url"
"strconv"
"strings"
)
var (
// DefaultHTTPPort Default HTTP Port used if only the protocol is provided to -H flag e.g. dockerd -H tcp://
// These are the IANA registered port numbers for use with Docker
// see http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml?search=docker
DefaultHTTPPort = 2375 // Default HTTP Port
// DefaultTLSHTTPPort Default HTTP Port used when TLS enabled
DefaultTLSHTTPPort = 2376 // Default TLS encrypted HTTP Port
// DefaultUnixSocket Path for the unix socket.
// Docker daemon by default always listens on the default unix socket
DefaultUnixSocket = "/var/run/docker.sock"
// DefaultTCPHost constant defines the default host string used by docker on Windows
DefaultTCPHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultHTTPPort)
// DefaultTLSHost constant defines the default host string used by docker for TLS sockets
DefaultTLSHost = fmt.Sprintf("tcp://%s:%d", DefaultHTTPHost, DefaultTLSHTTPPort)
// DefaultNamedPipe defines the default named pipe used by docker on Windows
DefaultNamedPipe = `//./pipe/docker_engine`
)
// ValidateHost validates that the specified string is a valid host and returns it.
func ValidateHost(val string) (string, error) {
host := strings.TrimSpace(val)
// The empty string means default and is not handled by parseDockerDaemonHost
if host != "" {
_, err := parseDockerDaemonHost(host)
if err != nil {
return val, err
}
}
// Note: unlike most flag validators, we don't return the mutated value here
// we need to know what the user entered later (using ParseHost) to adjust for TLS
return val, nil
}
// ParseHost and set defaults for a Daemon host string
func ParseHost(defaultToTLS bool, val string) (string, error) {
host := strings.TrimSpace(val)
if host == "" {
if defaultToTLS {
host = DefaultTLSHost
} else {
host = DefaultHost
}
} else {
var err error
host, err = parseDockerDaemonHost(host)
if err != nil {
return val, err
}
}
return host, nil
}
// parseDockerDaemonHost parses the specified address and returns an address that will be used as the host.
// Depending of the address specified, this may return one of the global Default* strings defined in hosts.go.
func parseDockerDaemonHost(addr string) (string, error) {
addrParts := strings.SplitN(addr, "://", 2)
if len(addrParts) == 1 && addrParts[0] != "" {
addrParts = []string{"tcp", addrParts[0]}
}
switch addrParts[0] {
case "tcp":
return ParseTCPAddr(addrParts[1], DefaultTCPHost)
case "unix":
return parseSimpleProtoAddr("unix", addrParts[1], DefaultUnixSocket)
case "npipe":
return parseSimpleProtoAddr("npipe", addrParts[1], DefaultNamedPipe)
case "fd":
return addr, nil
default:
return "", fmt.Errorf("Invalid bind address format: %s", addr)
}
}
// parseSimpleProtoAddr parses and validates that the specified address is a valid
// socket address for simple protocols like unix and npipe. It returns a formatted
// socket address, either using the address parsed from addr, or the contents of
// defaultAddr if addr is a blank string.
func parseSimpleProtoAddr(proto, addr, defaultAddr string) (string, error) {
addr = strings.TrimPrefix(addr, proto+"://")
if strings.Contains(addr, "://") {
return "", fmt.Errorf("Invalid proto, expected %s: %s", proto, addr)
}
if addr == "" {
addr = defaultAddr
}
return fmt.Sprintf("%s://%s", proto, addr), nil
}
// ParseTCPAddr parses and validates that the specified address is a valid TCP
// address. It returns a formatted TCP address, either using the address parsed
// from tryAddr, or the contents of defaultAddr if tryAddr is a blank string.
// tryAddr is expected to have already been Trim()'d
// defaultAddr must be in the full `tcp://host:port` form
func ParseTCPAddr(tryAddr string, defaultAddr string) (string, error) {
if tryAddr == "" || tryAddr == "tcp://" {
return defaultAddr, nil
}
addr := strings.TrimPrefix(tryAddr, "tcp://")
if strings.Contains(addr, "://") || addr == "" {
return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr)
}
defaultAddr = strings.TrimPrefix(defaultAddr, "tcp://")
defaultHost, defaultPort, err := net.SplitHostPort(defaultAddr)
if err != nil {
return "", err
}
// url.Parse fails for trailing colon on IPv6 brackets on Go 1.5, but
// not 1.4. See https://github.com/golang/go/issues/12200 and
// https://github.com/golang/go/issues/6530.
if strings.HasSuffix(addr, "]:") {
addr += defaultPort
}
u, err := url.Parse("tcp://" + addr)
if err != nil {
return "", err
}
host, port, err := net.SplitHostPort(u.Host)
if err != nil {
// try port addition once
host, port, err = net.SplitHostPort(net.JoinHostPort(u.Host, defaultPort))
}
if err != nil {
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
}
if host == "" {
host = defaultHost
}
if port == "" {
port = defaultPort
}
p, err := strconv.Atoi(port)
if err != nil && p == 0 {
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
}
return fmt.Sprintf("tcp://%s%s", net.JoinHostPort(host, port), u.Path), nil
}
// ValidateExtraHost validates that the specified string is a valid extrahost and returns it.
// ExtraHost is in the form of name:ip where the ip has to be a valid ip (IPv4 or IPv6).
func ValidateExtraHost(val string) (string, error) {
// allow for IPv6 addresses in extra hosts by only splitting on first ":"
arr := strings.SplitN(val, ":", 2)
if len(arr) != 2 || len(arr[0]) == 0 {
return "", fmt.Errorf("bad format for add-host: %q", val)
}
if _, err := ValidateIPAddress(arr[1]); err != nil {
return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1])
}
return val, nil
}

181
opts/hosts_test.go Normal file
View File

@ -0,0 +1,181 @@
package opts
import (
"fmt"
"strings"
"testing"
)
func TestParseHost(t *testing.T) {
invalid := []string{
"something with spaces",
"://",
"unknown://",
"tcp://:port",
"tcp://invalid:port",
}
valid := map[string]string{
"": DefaultHost,
" ": DefaultHost,
" ": DefaultHost,
"fd://": "fd://",
"fd://something": "fd://something",
"tcp://host:": fmt.Sprintf("tcp://host:%d", DefaultHTTPPort),
"tcp://": DefaultTCPHost,
"tcp://:2375": fmt.Sprintf("tcp://%s:2375", DefaultHTTPHost),
"tcp://:2376": fmt.Sprintf("tcp://%s:2376", DefaultHTTPHost),
"tcp://0.0.0.0:8080": "tcp://0.0.0.0:8080",
"tcp://192.168.0.0:12000": "tcp://192.168.0.0:12000",
"tcp://192.168:8080": "tcp://192.168:8080",
"tcp://0.0.0.0:1234567890": "tcp://0.0.0.0:1234567890", // yeah it's valid :P
" tcp://:7777/path ": fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost),
"tcp://docker.com:2375": "tcp://docker.com:2375",
"unix://": "unix://" + DefaultUnixSocket,
"unix://path/to/socket": "unix://path/to/socket",
"npipe://": "npipe://" + DefaultNamedPipe,
"npipe:////./pipe/foo": "npipe:////./pipe/foo",
}
for _, value := range invalid {
if _, err := ParseHost(false, value); err == nil {
t.Errorf("Expected an error for %v, got [nil]", value)
}
}
for value, expected := range valid {
if actual, err := ParseHost(false, value); err != nil || actual != expected {
t.Errorf("Expected for %v [%v], got [%v, %v]", value, expected, actual, err)
}
}
}
func TestParseDockerDaemonHost(t *testing.T) {
invalids := map[string]string{
"tcp:a.b.c.d": "Invalid bind address format: tcp:a.b.c.d",
"tcp:a.b.c.d/path": "Invalid bind address format: tcp:a.b.c.d/path",
"udp://127.0.0.1": "Invalid bind address format: udp://127.0.0.1",
"udp://127.0.0.1:2375": "Invalid bind address format: udp://127.0.0.1:2375",
"tcp://unix:///run/docker.sock": "Invalid proto, expected tcp: unix:///run/docker.sock",
" tcp://:7777/path ": "Invalid bind address format: tcp://:7777/path ",
"": "Invalid bind address format: ",
}
valids := map[string]string{
"0.0.0.1:": "tcp://0.0.0.1:2375",
"0.0.0.1:5555": "tcp://0.0.0.1:5555",
"0.0.0.1:5555/path": "tcp://0.0.0.1:5555/path",
"[::1]:": "tcp://[::1]:2375",
"[::1]:5555/path": "tcp://[::1]:5555/path",
"[0:0:0:0:0:0:0:1]:": "tcp://[0:0:0:0:0:0:0:1]:2375",
"[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path",
":6666": fmt.Sprintf("tcp://%s:6666", DefaultHTTPHost),
":6666/path": fmt.Sprintf("tcp://%s:6666/path", DefaultHTTPHost),
"tcp://": DefaultTCPHost,
"tcp://:7777": fmt.Sprintf("tcp://%s:7777", DefaultHTTPHost),
"tcp://:7777/path": fmt.Sprintf("tcp://%s:7777/path", DefaultHTTPHost),
"unix:///run/docker.sock": "unix:///run/docker.sock",
"unix://": "unix://" + DefaultUnixSocket,
"fd://": "fd://",
"fd://something": "fd://something",
"localhost:": "tcp://localhost:2375",
"localhost:5555": "tcp://localhost:5555",
"localhost:5555/path": "tcp://localhost:5555/path",
}
for invalidAddr, expectedError := range invalids {
if addr, err := parseDockerDaemonHost(invalidAddr); err == nil || err.Error() != expectedError {
t.Errorf("tcp %v address expected error %q return, got %q and addr %v", invalidAddr, expectedError, err, addr)
}
}
for validAddr, expectedAddr := range valids {
if addr, err := parseDockerDaemonHost(validAddr); err != nil || addr != expectedAddr {
t.Errorf("%v -> expected %v, got (%v) addr (%v)", validAddr, expectedAddr, err, addr)
}
}
}
func TestParseTCP(t *testing.T) {
var (
defaultHTTPHost = "tcp://127.0.0.1:2376"
)
invalids := map[string]string{
"tcp:a.b.c.d": "Invalid bind address format: tcp:a.b.c.d",
"tcp:a.b.c.d/path": "Invalid bind address format: tcp:a.b.c.d/path",
"udp://127.0.0.1": "Invalid proto, expected tcp: udp://127.0.0.1",
"udp://127.0.0.1:2375": "Invalid proto, expected tcp: udp://127.0.0.1:2375",
}
valids := map[string]string{
"": defaultHTTPHost,
"tcp://": defaultHTTPHost,
"0.0.0.1:": "tcp://0.0.0.1:2376",
"0.0.0.1:5555": "tcp://0.0.0.1:5555",
"0.0.0.1:5555/path": "tcp://0.0.0.1:5555/path",
":6666": "tcp://127.0.0.1:6666",
":6666/path": "tcp://127.0.0.1:6666/path",
"tcp://:7777": "tcp://127.0.0.1:7777",
"tcp://:7777/path": "tcp://127.0.0.1:7777/path",
"[::1]:": "tcp://[::1]:2376",
"[::1]:5555": "tcp://[::1]:5555",
"[::1]:5555/path": "tcp://[::1]:5555/path",
"[0:0:0:0:0:0:0:1]:": "tcp://[0:0:0:0:0:0:0:1]:2376",
"[0:0:0:0:0:0:0:1]:5555": "tcp://[0:0:0:0:0:0:0:1]:5555",
"[0:0:0:0:0:0:0:1]:5555/path": "tcp://[0:0:0:0:0:0:0:1]:5555/path",
"localhost:": "tcp://localhost:2376",
"localhost:5555": "tcp://localhost:5555",
"localhost:5555/path": "tcp://localhost:5555/path",
}
for invalidAddr, expectedError := range invalids {
if addr, err := ParseTCPAddr(invalidAddr, defaultHTTPHost); err == nil || err.Error() != expectedError {
t.Errorf("tcp %v address expected error %v return, got %s and addr %v", invalidAddr, expectedError, err, addr)
}
}
for validAddr, expectedAddr := range valids {
if addr, err := ParseTCPAddr(validAddr, defaultHTTPHost); err != nil || addr != expectedAddr {
t.Errorf("%v -> expected %v, got %v and addr %v", validAddr, expectedAddr, err, addr)
}
}
}
func TestParseInvalidUnixAddrInvalid(t *testing.T) {
if _, err := parseSimpleProtoAddr("unix", "tcp://127.0.0.1", "unix:///var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" {
t.Fatalf("Expected an error, got %v", err)
}
if _, err := parseSimpleProtoAddr("unix", "unix://tcp://127.0.0.1", "/var/run/docker.sock"); err == nil || err.Error() != "Invalid proto, expected unix: tcp://127.0.0.1" {
t.Fatalf("Expected an error, got %v", err)
}
if v, err := parseSimpleProtoAddr("unix", "", "/var/run/docker.sock"); err != nil || v != "unix:///var/run/docker.sock" {
t.Fatalf("Expected an %v, got %v", v, "unix:///var/run/docker.sock")
}
}
func TestValidateExtraHosts(t *testing.T) {
valid := []string{
`myhost:192.168.0.1`,
`thathost:10.0.2.1`,
`anipv6host:2003:ab34:e::1`,
`ipv6local:::1`,
}
invalid := map[string]string{
`myhost:192.notanipaddress.1`: `invalid IP`,
`thathost-nosemicolon10.0.0.1`: `bad format`,
`anipv6host:::::1`: `invalid IP`,
`ipv6local:::0::`: `invalid IP`,
}
for _, extrahost := range valid {
if _, err := ValidateExtraHost(extrahost); err != nil {
t.Fatalf("ValidateExtraHost(`"+extrahost+"`) should succeed: error %v", err)
}
}
for extraHost, expectedError := range invalid {
if _, err := ValidateExtraHost(extraHost); err == nil {
t.Fatalf("ValidateExtraHost(`%q`) should have failed validation", extraHost)
} else {
if !strings.Contains(err.Error(), expectedError) {
t.Fatalf("ValidateExtraHost(`%q`) error should contain %q", extraHost, expectedError)
}
}
}
}

8
opts/hosts_unix.go Normal file
View File

@ -0,0 +1,8 @@
// +build !windows
package opts
import "fmt"
// DefaultHost constant defines the default host string used by docker on other hosts than Windows
var DefaultHost = fmt.Sprintf("unix://%s", DefaultUnixSocket)

6
opts/hosts_windows.go Normal file
View File

@ -0,0 +1,6 @@
// +build windows
package opts
// DefaultHost constant defines the default host string used by docker on Windows
var DefaultHost = "npipe://" + DefaultNamedPipe

47
opts/ip.go Normal file
View File

@ -0,0 +1,47 @@
package opts
import (
"fmt"
"net"
)
// IPOpt holds an IP. It is used to store values from CLI flags.
type IPOpt struct {
*net.IP
}
// NewIPOpt creates a new IPOpt from a reference net.IP and a
// string representation of an IP. If the string is not a valid
// IP it will fallback to the specified reference.
func NewIPOpt(ref *net.IP, defaultVal string) *IPOpt {
o := &IPOpt{
IP: ref,
}
o.Set(defaultVal)
return o
}
// Set sets an IPv4 or IPv6 address from a given string. If the given
// string is not parseable as an IP address it returns an error.
func (o *IPOpt) Set(val string) error {
ip := net.ParseIP(val)
if ip == nil {
return fmt.Errorf("%s is not an ip address", val)
}
*o.IP = ip
return nil
}
// String returns the IP address stored in the IPOpt. If stored IP is a
// nil pointer, it returns an empty string.
func (o *IPOpt) String() string {
if *o.IP == nil {
return ""
}
return o.IP.String()
}
// Type returns the type of the option
func (o *IPOpt) Type() string {
return "ip"
}

54
opts/ip_test.go Normal file
View File

@ -0,0 +1,54 @@
package opts
import (
"net"
"testing"
)
func TestIpOptString(t *testing.T) {
addresses := []string{"", "0.0.0.0"}
var ip net.IP
for _, address := range addresses {
stringAddress := NewIPOpt(&ip, address).String()
if stringAddress != address {
t.Fatalf("IpOpt string should be `%s`, not `%s`", address, stringAddress)
}
}
}
func TestNewIpOptInvalidDefaultVal(t *testing.T) {
ip := net.IPv4(127, 0, 0, 1)
defaultVal := "Not an ip"
ipOpt := NewIPOpt(&ip, defaultVal)
expected := "127.0.0.1"
if ipOpt.String() != expected {
t.Fatalf("Expected [%v], got [%v]", expected, ipOpt.String())
}
}
func TestNewIpOptValidDefaultVal(t *testing.T) {
ip := net.IPv4(127, 0, 0, 1)
defaultVal := "192.168.1.1"
ipOpt := NewIPOpt(&ip, defaultVal)
expected := "192.168.1.1"
if ipOpt.String() != expected {
t.Fatalf("Expected [%v], got [%v]", expected, ipOpt.String())
}
}
func TestIpOptSetInvalidVal(t *testing.T) {
ip := net.IPv4(127, 0, 0, 1)
ipOpt := &IPOpt{IP: &ip}
invalidIP := "invalid ip"
expectedError := "invalid ip is not an ip address"
err := ipOpt.Set(invalidIP)
if err == nil || err.Error() != expectedError {
t.Fatalf("Expected an Error with [%v], got [%v]", expectedError, err.Error())
}
}

174
opts/mount.go Normal file
View File

@ -0,0 +1,174 @@
package opts
import (
"encoding/csv"
"fmt"
"os"
"strconv"
"strings"
mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/go-units"
)
// MountOpt is a Value type for parsing mounts
type MountOpt struct {
values []mounttypes.Mount
}
// Set a new mount value
// nolint: gocyclo
func (m *MountOpt) Set(value string) error {
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return err
}
mount := mounttypes.Mount{}
volumeOptions := func() *mounttypes.VolumeOptions {
if mount.VolumeOptions == nil {
mount.VolumeOptions = &mounttypes.VolumeOptions{
Labels: make(map[string]string),
}
}
if mount.VolumeOptions.DriverConfig == nil {
mount.VolumeOptions.DriverConfig = &mounttypes.Driver{}
}
return mount.VolumeOptions
}
bindOptions := func() *mounttypes.BindOptions {
if mount.BindOptions == nil {
mount.BindOptions = new(mounttypes.BindOptions)
}
return mount.BindOptions
}
tmpfsOptions := func() *mounttypes.TmpfsOptions {
if mount.TmpfsOptions == nil {
mount.TmpfsOptions = new(mounttypes.TmpfsOptions)
}
return mount.TmpfsOptions
}
setValueOnMap := func(target map[string]string, value string) {
parts := strings.SplitN(value, "=", 2)
if len(parts) == 1 {
target[value] = ""
} else {
target[parts[0]] = parts[1]
}
}
mount.Type = mounttypes.TypeVolume // default to volume mounts
// Set writable as the default
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
key := strings.ToLower(parts[0])
if len(parts) == 1 {
switch key {
case "readonly", "ro":
mount.ReadOnly = true
continue
case "volume-nocopy":
volumeOptions().NoCopy = true
continue
}
}
if len(parts) != 2 {
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
}
value := parts[1]
switch key {
case "type":
mount.Type = mounttypes.Type(strings.ToLower(value))
case "source", "src":
mount.Source = value
case "target", "dst", "destination":
mount.Target = value
case "readonly", "ro":
mount.ReadOnly, err = strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", key, value)
}
case "consistency":
mount.Consistency = mounttypes.Consistency(strings.ToLower(value))
case "bind-propagation":
bindOptions().Propagation = mounttypes.Propagation(strings.ToLower(value))
case "volume-nocopy":
volumeOptions().NoCopy, err = strconv.ParseBool(value)
if err != nil {
return fmt.Errorf("invalid value for volume-nocopy: %s", value)
}
case "volume-label":
setValueOnMap(volumeOptions().Labels, value)
case "volume-driver":
volumeOptions().DriverConfig.Name = value
case "volume-opt":
if volumeOptions().DriverConfig.Options == nil {
volumeOptions().DriverConfig.Options = make(map[string]string)
}
setValueOnMap(volumeOptions().DriverConfig.Options, value)
case "tmpfs-size":
sizeBytes, err := units.RAMInBytes(value)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", key, value)
}
tmpfsOptions().SizeBytes = sizeBytes
case "tmpfs-mode":
ui64, err := strconv.ParseUint(value, 8, 32)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", key, value)
}
tmpfsOptions().Mode = os.FileMode(ui64)
default:
return fmt.Errorf("unexpected key '%s' in '%s'", key, field)
}
}
if mount.Type == "" {
return fmt.Errorf("type is required")
}
if mount.Target == "" {
return fmt.Errorf("target is required")
}
if mount.VolumeOptions != nil && mount.Type != mounttypes.TypeVolume {
return fmt.Errorf("cannot mix 'volume-*' options with mount type '%s'", mount.Type)
}
if mount.BindOptions != nil && mount.Type != mounttypes.TypeBind {
return fmt.Errorf("cannot mix 'bind-*' options with mount type '%s'", mount.Type)
}
if mount.TmpfsOptions != nil && mount.Type != mounttypes.TypeTmpfs {
return fmt.Errorf("cannot mix 'tmpfs-*' options with mount type '%s'", mount.Type)
}
m.values = append(m.values, mount)
return nil
}
// Type returns the type of this option
func (m *MountOpt) Type() string {
return "mount"
}
// String returns a string repr of this option
func (m *MountOpt) String() string {
mounts := []string{}
for _, mount := range m.values {
repr := fmt.Sprintf("%s %s %s", mount.Type, mount.Source, mount.Target)
mounts = append(mounts, repr)
}
return strings.Join(mounts, ", ")
}
// Value returns the mounts
func (m *MountOpt) Value() []mounttypes.Mount {
return m.values
}

186
opts/mount_test.go Normal file
View File

@ -0,0 +1,186 @@
package opts
import (
"os"
"testing"
mounttypes "github.com/docker/docker/api/types/mount"
"github.com/docker/docker/pkg/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMountOptString(t *testing.T) {
mount := MountOpt{
values: []mounttypes.Mount{
{
Type: mounttypes.TypeBind,
Source: "/home/path",
Target: "/target",
},
{
Type: mounttypes.TypeVolume,
Source: "foo",
Target: "/target/foo",
},
},
}
expected := "bind /home/path /target, volume foo /target/foo"
assert.Equal(t, expected, mount.String())
}
func TestMountOptSetBindNoErrorBind(t *testing.T) {
for _, testcase := range []string{
// tests several aliases that should have same result.
"type=bind,target=/target,source=/source",
"type=bind,src=/source,dst=/target",
"type=bind,source=/source,dst=/target",
"type=bind,src=/source,target=/target",
} {
var mount MountOpt
assert.NoError(t, mount.Set(testcase))
mounts := mount.Value()
require.Len(t, mounts, 1)
assert.Equal(t, mounttypes.Mount{
Type: mounttypes.TypeBind,
Source: "/source",
Target: "/target",
}, mounts[0])
}
}
func TestMountOptSetVolumeNoError(t *testing.T) {
for _, testcase := range []string{
// tests several aliases that should have same result.
"type=volume,target=/target,source=/source",
"type=volume,src=/source,dst=/target",
"type=volume,source=/source,dst=/target",
"type=volume,src=/source,target=/target",
} {
var mount MountOpt
assert.NoError(t, mount.Set(testcase))
mounts := mount.Value()
require.Len(t, mounts, 1)
assert.Equal(t, mounttypes.Mount{
Type: mounttypes.TypeVolume,
Source: "/source",
Target: "/target",
}, mounts[0])
}
}
// TestMountOptDefaultType ensures that a mount without the type defaults to a
// volume mount.
func TestMountOptDefaultType(t *testing.T) {
var mount MountOpt
assert.NoError(t, mount.Set("target=/target,source=/foo"))
assert.Equal(t, mounttypes.TypeVolume, mount.values[0].Type)
}
func TestMountOptSetErrorNoTarget(t *testing.T) {
var mount MountOpt
assert.EqualError(t, mount.Set("type=volume,source=/foo"), "target is required")
}
func TestMountOptSetErrorInvalidKey(t *testing.T) {
var mount MountOpt
assert.EqualError(t, mount.Set("type=volume,bogus=foo"), "unexpected key 'bogus' in 'bogus=foo'")
}
func TestMountOptSetErrorInvalidField(t *testing.T) {
var mount MountOpt
assert.EqualError(t, mount.Set("type=volume,bogus"), "invalid field 'bogus' must be a key=value pair")
}
func TestMountOptSetErrorInvalidReadOnly(t *testing.T) {
var mount MountOpt
assert.EqualError(t, mount.Set("type=volume,readonly=no"), "invalid value for readonly: no")
assert.EqualError(t, mount.Set("type=volume,readonly=invalid"), "invalid value for readonly: invalid")
}
func TestMountOptDefaultEnableReadOnly(t *testing.T) {
var m MountOpt
assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo"))
assert.False(t, m.values[0].ReadOnly)
m = MountOpt{}
assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly"))
assert.True(t, m.values[0].ReadOnly)
m = MountOpt{}
assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=1"))
assert.True(t, m.values[0].ReadOnly)
m = MountOpt{}
assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=true"))
assert.True(t, m.values[0].ReadOnly)
m = MountOpt{}
assert.NoError(t, m.Set("type=bind,target=/foo,source=/foo,readonly=0"))
assert.False(t, m.values[0].ReadOnly)
}
func TestMountOptVolumeNoCopy(t *testing.T) {
var m MountOpt
assert.NoError(t, m.Set("type=volume,target=/foo,volume-nocopy"))
assert.Equal(t, "", m.values[0].Source)
m = MountOpt{}
assert.NoError(t, m.Set("type=volume,target=/foo,source=foo"))
assert.True(t, m.values[0].VolumeOptions == nil)
m = MountOpt{}
assert.NoError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=true"))
assert.True(t, m.values[0].VolumeOptions != nil)
assert.True(t, m.values[0].VolumeOptions.NoCopy)
m = MountOpt{}
assert.NoError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy"))
assert.True(t, m.values[0].VolumeOptions != nil)
assert.True(t, m.values[0].VolumeOptions.NoCopy)
m = MountOpt{}
assert.NoError(t, m.Set("type=volume,target=/foo,source=foo,volume-nocopy=1"))
assert.True(t, m.values[0].VolumeOptions != nil)
assert.True(t, m.values[0].VolumeOptions.NoCopy)
}
func TestMountOptTypeConflict(t *testing.T) {
var m MountOpt
testutil.ErrorContains(t, m.Set("type=bind,target=/foo,source=/foo,volume-nocopy=true"), "cannot mix")
testutil.ErrorContains(t, m.Set("type=volume,target=/foo,source=/foo,bind-propagation=rprivate"), "cannot mix")
}
func TestMountOptSetTmpfsNoError(t *testing.T) {
for _, testcase := range []string{
// tests several aliases that should have same result.
"type=tmpfs,target=/target,tmpfs-size=1m,tmpfs-mode=0700",
"type=tmpfs,target=/target,tmpfs-size=1MB,tmpfs-mode=700",
} {
var mount MountOpt
assert.NoError(t, mount.Set(testcase))
mounts := mount.Value()
require.Len(t, mounts, 1)
assert.Equal(t, mounttypes.Mount{
Type: mounttypes.TypeTmpfs,
Target: "/target",
TmpfsOptions: &mounttypes.TmpfsOptions{
SizeBytes: 1024 * 1024, // not 1000 * 1000
Mode: os.FileMode(0700),
},
}, mounts[0])
}
}
func TestMountOptSetTmpfsError(t *testing.T) {
var m MountOpt
testutil.ErrorContains(t, m.Set("type=tmpfs,target=/foo,tmpfs-size=foo"), "invalid value for tmpfs-size")
testutil.ErrorContains(t, m.Set("type=tmpfs,target=/foo,tmpfs-mode=foo"), "invalid value for tmpfs-mode")
testutil.ErrorContains(t, m.Set("type=tmpfs"), "target is required")
}

488
opts/opts.go Normal file
View File

@ -0,0 +1,488 @@
package opts
import (
"fmt"
"math/big"
"net"
"path"
"regexp"
"strings"
"github.com/docker/docker/api/types/filters"
units "github.com/docker/go-units"
)
var (
alphaRegexp = regexp.MustCompile(`[a-zA-Z]`)
domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`)
)
// ListOpts holds a list of values and a validation function.
type ListOpts struct {
values *[]string
validator ValidatorFctType
}
// NewListOpts creates a new ListOpts with the specified validator.
func NewListOpts(validator ValidatorFctType) ListOpts {
var values []string
return *NewListOptsRef(&values, validator)
}
// NewListOptsRef creates a new ListOpts with the specified values and validator.
func NewListOptsRef(values *[]string, validator ValidatorFctType) *ListOpts {
return &ListOpts{
values: values,
validator: validator,
}
}
func (opts *ListOpts) String() string {
if len(*opts.values) == 0 {
return ""
}
return fmt.Sprintf("%v", *opts.values)
}
// Set validates if needed the input value and adds it to the
// internal slice.
func (opts *ListOpts) Set(value string) error {
if opts.validator != nil {
v, err := opts.validator(value)
if err != nil {
return err
}
value = v
}
(*opts.values) = append((*opts.values), value)
return nil
}
// Delete removes the specified element from the slice.
func (opts *ListOpts) Delete(key string) {
for i, k := range *opts.values {
if k == key {
(*opts.values) = append((*opts.values)[:i], (*opts.values)[i+1:]...)
return
}
}
}
// GetMap returns the content of values in a map in order to avoid
// duplicates.
func (opts *ListOpts) GetMap() map[string]struct{} {
ret := make(map[string]struct{})
for _, k := range *opts.values {
ret[k] = struct{}{}
}
return ret
}
// GetAll returns the values of slice.
func (opts *ListOpts) GetAll() []string {
return (*opts.values)
}
// GetAllOrEmpty returns the values of the slice
// or an empty slice when there are no values.
func (opts *ListOpts) GetAllOrEmpty() []string {
v := *opts.values
if v == nil {
return make([]string, 0)
}
return v
}
// Get checks the existence of the specified key.
func (opts *ListOpts) Get(key string) bool {
for _, k := range *opts.values {
if k == key {
return true
}
}
return false
}
// Len returns the amount of element in the slice.
func (opts *ListOpts) Len() int {
return len((*opts.values))
}
// Type returns a string name for this Option type
func (opts *ListOpts) Type() string {
return "list"
}
// WithValidator returns the ListOpts with validator set.
func (opts *ListOpts) WithValidator(validator ValidatorFctType) *ListOpts {
opts.validator = validator
return opts
}
// NamedOption is an interface that list and map options
// with names implement.
type NamedOption interface {
Name() string
}
// NamedListOpts is a ListOpts with a configuration name.
// This struct is useful to keep reference to the assigned
// field name in the internal configuration struct.
type NamedListOpts struct {
name string
ListOpts
}
var _ NamedOption = &NamedListOpts{}
// NewNamedListOptsRef creates a reference to a new NamedListOpts struct.
func NewNamedListOptsRef(name string, values *[]string, validator ValidatorFctType) *NamedListOpts {
return &NamedListOpts{
name: name,
ListOpts: *NewListOptsRef(values, validator),
}
}
// Name returns the name of the NamedListOpts in the configuration.
func (o *NamedListOpts) Name() string {
return o.name
}
// MapOpts holds a map of values and a validation function.
type MapOpts struct {
values map[string]string
validator ValidatorFctType
}
// Set validates if needed the input value and add it to the
// internal map, by splitting on '='.
func (opts *MapOpts) Set(value string) error {
if opts.validator != nil {
v, err := opts.validator(value)
if err != nil {
return err
}
value = v
}
vals := strings.SplitN(value, "=", 2)
if len(vals) == 1 {
(opts.values)[vals[0]] = ""
} else {
(opts.values)[vals[0]] = vals[1]
}
return nil
}
// GetAll returns the values of MapOpts as a map.
func (opts *MapOpts) GetAll() map[string]string {
return opts.values
}
func (opts *MapOpts) String() string {
return fmt.Sprintf("%v", map[string]string((opts.values)))
}
// Type returns a string name for this Option type
func (opts *MapOpts) Type() string {
return "map"
}
// NewMapOpts creates a new MapOpts with the specified map of values and a validator.
func NewMapOpts(values map[string]string, validator ValidatorFctType) *MapOpts {
if values == nil {
values = make(map[string]string)
}
return &MapOpts{
values: values,
validator: validator,
}
}
// NamedMapOpts is a MapOpts struct with a configuration name.
// This struct is useful to keep reference to the assigned
// field name in the internal configuration struct.
type NamedMapOpts struct {
name string
MapOpts
}
var _ NamedOption = &NamedMapOpts{}
// NewNamedMapOpts creates a reference to a new NamedMapOpts struct.
func NewNamedMapOpts(name string, values map[string]string, validator ValidatorFctType) *NamedMapOpts {
return &NamedMapOpts{
name: name,
MapOpts: *NewMapOpts(values, validator),
}
}
// Name returns the name of the NamedMapOpts in the configuration.
func (o *NamedMapOpts) Name() string {
return o.name
}
// ValidatorFctType defines a validator function that returns a validated string and/or an error.
type ValidatorFctType func(val string) (string, error)
// ValidatorFctListType defines a validator function that returns a validated list of string and/or an error
type ValidatorFctListType func(val string) ([]string, error)
// ValidateIPAddress validates an Ip address.
func ValidateIPAddress(val string) (string, error) {
var ip = net.ParseIP(strings.TrimSpace(val))
if ip != nil {
return ip.String(), nil
}
return "", fmt.Errorf("%s is not an ip address", val)
}
// ValidateMACAddress validates a MAC address.
func ValidateMACAddress(val string) (string, error) {
_, err := net.ParseMAC(strings.TrimSpace(val))
if err != nil {
return "", err
}
return val, nil
}
// ValidateDNSSearch validates domain for resolvconf search configuration.
// A zero length domain is represented by a dot (.).
func ValidateDNSSearch(val string) (string, error) {
if val = strings.Trim(val, " "); val == "." {
return val, nil
}
return validateDomain(val)
}
func validateDomain(val string) (string, error) {
if alphaRegexp.FindString(val) == "" {
return "", fmt.Errorf("%s is not a valid domain", val)
}
ns := domainRegexp.FindSubmatch([]byte(val))
if len(ns) > 0 && len(ns[1]) < 255 {
return string(ns[1]), nil
}
return "", fmt.Errorf("%s is not a valid domain", val)
}
// ValidateLabel validates that the specified string is a valid label, and returns it.
// Labels are in the form on key=value.
func ValidateLabel(val string) (string, error) {
if strings.Count(val, "=") < 1 {
return "", fmt.Errorf("bad attribute format: %s", val)
}
return val, nil
}
// ValidateSysctl validates a sysctl and returns it.
func ValidateSysctl(val string) (string, error) {
validSysctlMap := map[string]bool{
"kernel.msgmax": true,
"kernel.msgmnb": true,
"kernel.msgmni": true,
"kernel.sem": true,
"kernel.shmall": true,
"kernel.shmmax": true,
"kernel.shmmni": true,
"kernel.shm_rmid_forced": true,
}
validSysctlPrefixes := []string{
"net.",
"fs.mqueue.",
}
arr := strings.Split(val, "=")
if len(arr) < 2 {
return "", fmt.Errorf("sysctl '%s' is not whitelisted", val)
}
if validSysctlMap[arr[0]] {
return val, nil
}
for _, vp := range validSysctlPrefixes {
if strings.HasPrefix(arr[0], vp) {
return val, nil
}
}
return "", fmt.Errorf("sysctl '%s' is not whitelisted", val)
}
// FilterOpt is a flag type for validating filters
type FilterOpt struct {
filter filters.Args
}
// NewFilterOpt returns a new FilterOpt
func NewFilterOpt() FilterOpt {
return FilterOpt{filter: filters.NewArgs()}
}
func (o *FilterOpt) String() string {
repr, err := filters.ToParam(o.filter)
if err != nil {
return "invalid filters"
}
return repr
}
// Set sets the value of the opt by parsing the command line value
func (o *FilterOpt) Set(value string) error {
var err error
o.filter, err = filters.ParseFlag(value, o.filter)
return err
}
// Type returns the option type
func (o *FilterOpt) Type() string {
return "filter"
}
// Value returns the value of this option
func (o *FilterOpt) Value() filters.Args {
return o.filter
}
// NanoCPUs is a type for fixed point fractional number.
type NanoCPUs int64
// String returns the string format of the number
func (c *NanoCPUs) String() string {
if *c == 0 {
return ""
}
return big.NewRat(c.Value(), 1e9).FloatString(3)
}
// Set sets the value of the NanoCPU by passing a string
func (c *NanoCPUs) Set(value string) error {
cpus, err := ParseCPUs(value)
*c = NanoCPUs(cpus)
return err
}
// Type returns the type
func (c *NanoCPUs) Type() string {
return "decimal"
}
// Value returns the value in int64
func (c *NanoCPUs) Value() int64 {
return int64(*c)
}
// ParseCPUs takes a string ratio and returns an integer value of nano cpus
func ParseCPUs(value string) (int64, error) {
cpu, ok := new(big.Rat).SetString(value)
if !ok {
return 0, fmt.Errorf("failed to parse %v as a rational number", value)
}
nano := cpu.Mul(cpu, big.NewRat(1e9, 1))
if !nano.IsInt() {
return 0, fmt.Errorf("value is too precise")
}
return nano.Num().Int64(), nil
}
// ParseLink parses and validates the specified string as a link format (name:alias)
func ParseLink(val string) (string, string, error) {
if val == "" {
return "", "", fmt.Errorf("empty string specified for links")
}
arr := strings.Split(val, ":")
if len(arr) > 2 {
return "", "", fmt.Errorf("bad format for links: %s", val)
}
if len(arr) == 1 {
return val, val, nil
}
// This is kept because we can actually get a HostConfig with links
// from an already created container and the format is not `foo:bar`
// but `/foo:/c1/bar`
if strings.HasPrefix(arr[0], "/") {
_, alias := path.Split(arr[1])
return arr[0][1:], alias, nil
}
return arr[0], arr[1], nil
}
// ValidateLink validates that the specified string has a valid link format (containerName:alias).
func ValidateLink(val string) (string, error) {
_, _, err := ParseLink(val)
return val, err
}
// MemBytes is a type for human readable memory bytes (like 128M, 2g, etc)
type MemBytes int64
// String returns the string format of the human readable memory bytes
func (m *MemBytes) String() string {
// NOTE: In spf13/pflag/flag.go, "0" is considered as "zero value" while "0 B" is not.
// We return "0" in case value is 0 here so that the default value is hidden.
// (Sometimes "default 0 B" is actually misleading)
if m.Value() != 0 {
return units.BytesSize(float64(m.Value()))
}
return "0"
}
// Set sets the value of the MemBytes by passing a string
func (m *MemBytes) Set(value string) error {
val, err := units.RAMInBytes(value)
*m = MemBytes(val)
return err
}
// Type returns the type
func (m *MemBytes) Type() string {
return "bytes"
}
// Value returns the value in int64
func (m *MemBytes) Value() int64 {
return int64(*m)
}
// UnmarshalJSON is the customized unmarshaler for MemBytes
func (m *MemBytes) UnmarshalJSON(s []byte) error {
if len(s) <= 2 || s[0] != '"' || s[len(s)-1] != '"' {
return fmt.Errorf("invalid size: %q", s)
}
val, err := units.RAMInBytes(string(s[1 : len(s)-1]))
*m = MemBytes(val)
return err
}
// MemSwapBytes is a type for human readable memory bytes (like 128M, 2g, etc).
// It differs from MemBytes in that -1 is valid and the default.
type MemSwapBytes int64
// Set sets the value of the MemSwapBytes by passing a string
func (m *MemSwapBytes) Set(value string) error {
if value == "-1" {
*m = MemSwapBytes(-1)
return nil
}
val, err := units.RAMInBytes(value)
*m = MemSwapBytes(val)
return err
}
// Type returns the type
func (m *MemSwapBytes) Type() string {
return "bytes"
}
// Value returns the value in int64
func (m *MemSwapBytes) Value() int64 {
return int64(*m)
}
func (m *MemSwapBytes) String() string {
b := MemBytes(*m)
return b.String()
}
// UnmarshalJSON is the customized unmarshaler for MemSwapBytes
func (m *MemSwapBytes) UnmarshalJSON(s []byte) error {
b := MemBytes(*m)
return b.UnmarshalJSON(s)
}

308
opts/opts_test.go Normal file
View File

@ -0,0 +1,308 @@
package opts
import (
"fmt"
"strings"
"testing"
)
func TestValidateIPAddress(t *testing.T) {
if ret, err := ValidateIPAddress(`1.2.3.4`); err != nil || ret == "" {
t.Fatalf("ValidateIPAddress(`1.2.3.4`) got %s %s", ret, err)
}
if ret, err := ValidateIPAddress(`127.0.0.1`); err != nil || ret == "" {
t.Fatalf("ValidateIPAddress(`127.0.0.1`) got %s %s", ret, err)
}
if ret, err := ValidateIPAddress(`::1`); err != nil || ret == "" {
t.Fatalf("ValidateIPAddress(`::1`) got %s %s", ret, err)
}
if ret, err := ValidateIPAddress(`127`); err == nil || ret != "" {
t.Fatalf("ValidateIPAddress(`127`) got %s %s", ret, err)
}
if ret, err := ValidateIPAddress(`random invalid string`); err == nil || ret != "" {
t.Fatalf("ValidateIPAddress(`random invalid string`) got %s %s", ret, err)
}
}
func TestMapOpts(t *testing.T) {
tmpMap := make(map[string]string)
o := NewMapOpts(tmpMap, logOptsValidator)
o.Set("max-size=1")
if o.String() != "map[max-size:1]" {
t.Errorf("%s != [map[max-size:1]", o.String())
}
o.Set("max-file=2")
if len(tmpMap) != 2 {
t.Errorf("map length %d != 2", len(tmpMap))
}
if tmpMap["max-file"] != "2" {
t.Errorf("max-file = %s != 2", tmpMap["max-file"])
}
if tmpMap["max-size"] != "1" {
t.Errorf("max-size = %s != 1", tmpMap["max-size"])
}
if o.Set("dummy-val=3") == nil {
t.Error("validator is not being called")
}
}
func TestListOptsWithoutValidator(t *testing.T) {
o := NewListOpts(nil)
o.Set("foo")
if o.String() != "[foo]" {
t.Errorf("%s != [foo]", o.String())
}
o.Set("bar")
if o.Len() != 2 {
t.Errorf("%d != 2", o.Len())
}
o.Set("bar")
if o.Len() != 3 {
t.Errorf("%d != 3", o.Len())
}
if !o.Get("bar") {
t.Error("o.Get(\"bar\") == false")
}
if o.Get("baz") {
t.Error("o.Get(\"baz\") == true")
}
o.Delete("foo")
if o.String() != "[bar bar]" {
t.Errorf("%s != [bar bar]", o.String())
}
listOpts := o.GetAll()
if len(listOpts) != 2 || listOpts[0] != "bar" || listOpts[1] != "bar" {
t.Errorf("Expected [[bar bar]], got [%v]", listOpts)
}
mapListOpts := o.GetMap()
if len(mapListOpts) != 1 {
t.Errorf("Expected [map[bar:{}]], got [%v]", mapListOpts)
}
}
func TestListOptsWithValidator(t *testing.T) {
// Re-using logOptsvalidator (used by MapOpts)
o := NewListOpts(logOptsValidator)
o.Set("foo")
if o.String() != "" {
t.Errorf(`%s != ""`, o.String())
}
o.Set("foo=bar")
if o.String() != "" {
t.Errorf(`%s != ""`, o.String())
}
o.Set("max-file=2")
if o.Len() != 1 {
t.Errorf("%d != 1", o.Len())
}
if !o.Get("max-file=2") {
t.Error("o.Get(\"max-file=2\") == false")
}
if o.Get("baz") {
t.Error("o.Get(\"baz\") == true")
}
o.Delete("max-file=2")
if o.String() != "" {
t.Errorf(`%s != ""`, o.String())
}
}
// nolint: lll
func TestValidateDNSSearch(t *testing.T) {
valid := []string{
`.`,
`a`,
`a.`,
`1.foo`,
`17.foo`,
`foo.bar`,
`foo.bar.baz`,
`foo.bar.`,
`foo.bar.baz`,
`foo1.bar2`,
`foo1.bar2.baz`,
`1foo.2bar.`,
`1foo.2bar.baz`,
`foo-1.bar-2`,
`foo-1.bar-2.baz`,
`foo-1.bar-2.`,
`foo-1.bar-2.baz`,
`1-foo.2-bar`,
`1-foo.2-bar.baz`,
`1-foo.2-bar.`,
`1-foo.2-bar.baz`,
}
invalid := []string{
``,
` `,
` `,
`17`,
`17.`,
`.17`,
`17-.`,
`17-.foo`,
`.foo`,
`foo-.bar`,
`-foo.bar`,
`foo.bar-`,
`foo.bar-.baz`,
`foo.-bar`,
`foo.-bar.baz`,
`foo.bar.baz.this.should.fail.on.long.name.beause.it.is.longer.thanisshouldbethis.should.fail.on.long.name.beause.it.is.longer.thanisshouldbethis.should.fail.on.long.name.beause.it.is.longer.thanisshouldbethis.should.fail.on.long.name.beause.it.is.longer.thanisshouldbe`,
}
for _, domain := range valid {
if ret, err := ValidateDNSSearch(domain); err != nil || ret == "" {
t.Fatalf("ValidateDNSSearch(`"+domain+"`) got %s %s", ret, err)
}
}
for _, domain := range invalid {
if ret, err := ValidateDNSSearch(domain); err == nil || ret != "" {
t.Fatalf("ValidateDNSSearch(`"+domain+"`) got %s %s", ret, err)
}
}
}
func TestValidateLabel(t *testing.T) {
if _, err := ValidateLabel("label"); err == nil || err.Error() != "bad attribute format: label" {
t.Fatalf("Expected an error [bad attribute format: label], go %v", err)
}
if actual, err := ValidateLabel("key1=value1"); err != nil || actual != "key1=value1" {
t.Fatalf("Expected [key1=value1], got [%v,%v]", actual, err)
}
// Validate it's working with more than one =
if actual, err := ValidateLabel("key1=value1=value2"); err != nil {
t.Fatalf("Expected [key1=value1=value2], got [%v,%v]", actual, err)
}
// Validate it's working with one more
if actual, err := ValidateLabel("key1=value1=value2=value3"); err != nil {
t.Fatalf("Expected [key1=value1=value2=value2], got [%v,%v]", actual, err)
}
}
func logOptsValidator(val string) (string, error) {
allowedKeys := map[string]string{"max-size": "1", "max-file": "2"}
vals := strings.Split(val, "=")
if allowedKeys[vals[0]] != "" {
return val, nil
}
return "", fmt.Errorf("invalid key %s", vals[0])
}
func TestNamedListOpts(t *testing.T) {
var v []string
o := NewNamedListOptsRef("foo-name", &v, nil)
o.Set("foo")
if o.String() != "[foo]" {
t.Errorf("%s != [foo]", o.String())
}
if o.Name() != "foo-name" {
t.Errorf("%s != foo-name", o.Name())
}
if len(v) != 1 {
t.Errorf("expected foo to be in the values, got %v", v)
}
}
func TestNamedMapOpts(t *testing.T) {
tmpMap := make(map[string]string)
o := NewNamedMapOpts("max-name", tmpMap, nil)
o.Set("max-size=1")
if o.String() != "map[max-size:1]" {
t.Errorf("%s != [map[max-size:1]", o.String())
}
if o.Name() != "max-name" {
t.Errorf("%s != max-name", o.Name())
}
if _, exist := tmpMap["max-size"]; !exist {
t.Errorf("expected map-size to be in the values, got %v", tmpMap)
}
}
func TestValidateMACAddress(t *testing.T) {
if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil {
t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err)
}
if _, err := ValidateMACAddress(`92:d0:c6:0a:33`); err == nil {
t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:33`) succeeded; expected failure on invalid MAC")
}
if _, err := ValidateMACAddress(`random invalid string`); err == nil {
t.Fatalf("ValidateMACAddress(`random invalid string`) succeeded; expected failure on invalid MAC")
}
}
func TestValidateLink(t *testing.T) {
valid := []string{
"name",
"dcdfbe62ecd0:alias",
"7a67485460b7642516a4ad82ecefe7f57d0c4916f530561b71a50a3f9c4e33da",
"angry_torvalds:linus",
}
invalid := map[string]string{
"": "empty string specified for links",
"too:much:of:it": "bad format for links: too:much:of:it",
}
for _, link := range valid {
if _, err := ValidateLink(link); err != nil {
t.Fatalf("ValidateLink(`%q`) should succeed: error %q", link, err)
}
}
for link, expectedError := range invalid {
if _, err := ValidateLink(link); err == nil {
t.Fatalf("ValidateLink(`%q`) should have failed validation", link)
} else {
if !strings.Contains(err.Error(), expectedError) {
t.Fatalf("ValidateLink(`%q`) error should contain %q", link, expectedError)
}
}
}
}
func TestParseLink(t *testing.T) {
name, alias, err := ParseLink("name:alias")
if err != nil {
t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
}
if name != "name" {
t.Fatalf("Link name should have been name, got %s instead", name)
}
if alias != "alias" {
t.Fatalf("Link alias should have been alias, got %s instead", alias)
}
// short format definition
name, alias, err = ParseLink("name")
if err != nil {
t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
}
if name != "name" {
t.Fatalf("Link name should have been name, got %s instead", name)
}
if alias != "name" {
t.Fatalf("Link alias should have been name, got %s instead", alias)
}
// empty string link definition is not allowed
if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
}
// more than two colons are not allowed
if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
}
}

6
opts/opts_unix.go Normal file
View File

@ -0,0 +1,6 @@
// +build !windows
package opts
// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. dockerd -H tcp://:8080
const DefaultHTTPHost = "localhost"

56
opts/opts_windows.go Normal file
View File

@ -0,0 +1,56 @@
package opts
// TODO Windows. Identify bug in GOLang 1.5.1+ and/or Windows Server 2016 TP5.
// @jhowardmsft, @swernli.
//
// On Windows, this mitigates a problem with the default options of running
// a docker client against a local docker daemon on TP5.
//
// What was found that if the default host is "localhost", even if the client
// (and daemon as this is local) is not physically on a network, and the DNS
// cache is flushed (ipconfig /flushdns), then the client will pause for
// exactly one second when connecting to the daemon for calls. For example
// using docker run windowsservercore cmd, the CLI will send a create followed
// by an attach. You see the delay between the attach finishing and the attach
// being seen by the daemon.
//
// Here's some daemon debug logs with additional debug spew put in. The
// AfterWriteJSON log is the very last thing the daemon does as part of the
// create call. The POST /attach is the second CLI call. Notice the second
// time gap.
//
// time="2015-11-06T13:38:37.259627400-08:00" level=debug msg="After createRootfs"
// time="2015-11-06T13:38:37.263626300-08:00" level=debug msg="After setHostConfig"
// time="2015-11-06T13:38:37.267631200-08:00" level=debug msg="before createContainerPl...."
// time="2015-11-06T13:38:37.271629500-08:00" level=debug msg=ToDiskLocking....
// time="2015-11-06T13:38:37.275643200-08:00" level=debug msg="loggin event...."
// time="2015-11-06T13:38:37.277627600-08:00" level=debug msg="logged event...."
// time="2015-11-06T13:38:37.279631800-08:00" level=debug msg="In defer func"
// time="2015-11-06T13:38:37.282628100-08:00" level=debug msg="After daemon.create"
// time="2015-11-06T13:38:37.286651700-08:00" level=debug msg="return 2"
// time="2015-11-06T13:38:37.289629500-08:00" level=debug msg="Returned from daemon.ContainerCreate"
// time="2015-11-06T13:38:37.311629100-08:00" level=debug msg="After WriteJSON"
// ... 1 second gap here....
// time="2015-11-06T13:38:38.317866200-08:00" level=debug msg="Calling POST /v1.22/containers/984758282b842f779e805664b2c95d563adc9a979c8a3973e68c807843ee4757/attach"
// time="2015-11-06T13:38:38.326882500-08:00" level=info msg="POST /v1.22/containers/984758282b842f779e805664b2c95d563adc9a979c8a3973e68c807843ee4757/attach?stderr=1&stdin=1&stdout=1&stream=1"
//
// We suspect this is either a bug introduced in GOLang 1.5.1, or that a change
// in GOLang 1.5.1 (from 1.4.3) is exposing a bug in Windows. In theory,
// the Windows networking stack is supposed to resolve "localhost" internally,
// without hitting DNS, or even reading the hosts file (which is why localhost
// is commented out in the hosts file on Windows).
//
// We have validated that working around this using the actual IPv4 localhost
// address does not cause the delay.
//
// This does not occur with the docker client built with 1.4.3 on the same
// Windows build, regardless of whether the daemon is built using 1.5.1
// or 1.4.3. It does not occur on Linux. We also verified we see the same thing
// on a cross-compiled Windows binary (from Linux).
//
// Final note: This is a mitigation, not a 'real' fix. It is still susceptible
// to the delay if a user were to do 'docker run -H=tcp://localhost:2375...'
// explicitly.
// DefaultHTTPHost Default HTTP Host used if only port is provided to -H flag e.g. dockerd -H tcp://:8080
const DefaultHTTPHost = "127.0.0.1"

163
opts/port.go Normal file
View File

@ -0,0 +1,163 @@
package opts
import (
"encoding/csv"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/go-connections/nat"
)
const (
portOptTargetPort = "target"
portOptPublishedPort = "published"
portOptProtocol = "protocol"
portOptMode = "mode"
)
// PortOpt represents a port config in swarm mode.
type PortOpt struct {
ports []swarm.PortConfig
}
// Set a new port value
// nolint: gocyclo
func (p *PortOpt) Set(value string) error {
longSyntax, err := regexp.MatchString(`\w+=\w+(,\w+=\w+)*`, value)
if err != nil {
return err
}
if longSyntax {
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return err
}
pConfig := swarm.PortConfig{}
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("invalid field %s", field)
}
key := strings.ToLower(parts[0])
value := strings.ToLower(parts[1])
switch key {
case portOptProtocol:
if value != string(swarm.PortConfigProtocolTCP) && value != string(swarm.PortConfigProtocolUDP) {
return fmt.Errorf("invalid protocol value %s", value)
}
pConfig.Protocol = swarm.PortConfigProtocol(value)
case portOptMode:
if value != string(swarm.PortConfigPublishModeIngress) && value != string(swarm.PortConfigPublishModeHost) {
return fmt.Errorf("invalid publish mode value %s", value)
}
pConfig.PublishMode = swarm.PortConfigPublishMode(value)
case portOptTargetPort:
tPort, err := strconv.ParseUint(value, 10, 16)
if err != nil {
return err
}
pConfig.TargetPort = uint32(tPort)
case portOptPublishedPort:
pPort, err := strconv.ParseUint(value, 10, 16)
if err != nil {
return err
}
pConfig.PublishedPort = uint32(pPort)
default:
return fmt.Errorf("invalid field key %s", key)
}
}
if pConfig.TargetPort == 0 {
return fmt.Errorf("missing mandatory field %q", portOptTargetPort)
}
if pConfig.PublishMode == "" {
pConfig.PublishMode = swarm.PortConfigPublishModeIngress
}
if pConfig.Protocol == "" {
pConfig.Protocol = swarm.PortConfigProtocolTCP
}
p.ports = append(p.ports, pConfig)
} else {
// short syntax
portConfigs := []swarm.PortConfig{}
ports, portBindingMap, err := nat.ParsePortSpecs([]string{value})
if err != nil {
return err
}
for _, portBindings := range portBindingMap {
for _, portBinding := range portBindings {
if portBinding.HostIP != "" {
return fmt.Errorf("hostip is not supported")
}
}
}
for port := range ports {
portConfig, err := ConvertPortToPortConfig(port, portBindingMap)
if err != nil {
return err
}
portConfigs = append(portConfigs, portConfig...)
}
p.ports = append(p.ports, portConfigs...)
}
return nil
}
// Type returns the type of this option
func (p *PortOpt) Type() string {
return "port"
}
// String returns a string repr of this option
func (p *PortOpt) String() string {
ports := []string{}
for _, port := range p.ports {
repr := fmt.Sprintf("%v:%v/%s/%s", port.PublishedPort, port.TargetPort, port.Protocol, port.PublishMode)
ports = append(ports, repr)
}
return strings.Join(ports, ", ")
}
// Value returns the ports
func (p *PortOpt) Value() []swarm.PortConfig {
return p.ports
}
// ConvertPortToPortConfig converts ports to the swarm type
func ConvertPortToPortConfig(
port nat.Port,
portBindings map[nat.Port][]nat.PortBinding,
) ([]swarm.PortConfig, error) {
ports := []swarm.PortConfig{}
for _, binding := range portBindings[port] {
hostPort, err := strconv.ParseUint(binding.HostPort, 10, 16)
if err != nil && binding.HostPort != "" {
return nil, fmt.Errorf("invalid hostport binding (%s) for port (%s)", binding.HostPort, port.Port())
}
ports = append(ports, swarm.PortConfig{
//TODO Name: ?
Protocol: swarm.PortConfigProtocol(strings.ToLower(port.Proto())),
TargetPort: uint32(port.Int()),
PublishedPort: uint32(hostPort),
PublishMode: swarm.PortConfigPublishModeIngress,
})
}
return ports, nil
}

296
opts/port_test.go Normal file
View File

@ -0,0 +1,296 @@
package opts
import (
"testing"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/pkg/testutil"
"github.com/stretchr/testify/assert"
)
func TestPortOptValidSimpleSyntax(t *testing.T) {
testCases := []struct {
value string
expected []swarm.PortConfig
}{
{
value: "80",
expected: []swarm.PortConfig{
{
Protocol: "tcp",
TargetPort: 80,
PublishMode: swarm.PortConfigPublishModeIngress,
},
},
},
{
value: "80:8080",
expected: []swarm.PortConfig{
{
Protocol: "tcp",
TargetPort: 8080,
PublishedPort: 80,
PublishMode: swarm.PortConfigPublishModeIngress,
},
},
},
{
value: "8080:80/tcp",
expected: []swarm.PortConfig{
{
Protocol: "tcp",
TargetPort: 80,
PublishedPort: 8080,
PublishMode: swarm.PortConfigPublishModeIngress,
},
},
},
{
value: "80:8080/udp",
expected: []swarm.PortConfig{
{
Protocol: "udp",
TargetPort: 8080,
PublishedPort: 80,
PublishMode: swarm.PortConfigPublishModeIngress,
},
},
},
{
value: "80-81:8080-8081/tcp",
expected: []swarm.PortConfig{
{
Protocol: "tcp",
TargetPort: 8080,
PublishedPort: 80,
PublishMode: swarm.PortConfigPublishModeIngress,
},
{
Protocol: "tcp",
TargetPort: 8081,
PublishedPort: 81,
PublishMode: swarm.PortConfigPublishModeIngress,
},
},
},
{
value: "80-82:8080-8082/udp",
expected: []swarm.PortConfig{
{
Protocol: "udp",
TargetPort: 8080,
PublishedPort: 80,
PublishMode: swarm.PortConfigPublishModeIngress,
},
{
Protocol: "udp",
TargetPort: 8081,
PublishedPort: 81,
PublishMode: swarm.PortConfigPublishModeIngress,
},
{
Protocol: "udp",
TargetPort: 8082,
PublishedPort: 82,
PublishMode: swarm.PortConfigPublishModeIngress,
},
},
},
}
for _, tc := range testCases {
var port PortOpt
assert.NoError(t, port.Set(tc.value))
assert.Len(t, port.Value(), len(tc.expected))
for _, expectedPortConfig := range tc.expected {
assertContains(t, port.Value(), expectedPortConfig)
}
}
}
func TestPortOptValidComplexSyntax(t *testing.T) {
testCases := []struct {
value string
expected []swarm.PortConfig
}{
{
value: "target=80",
expected: []swarm.PortConfig{
{
TargetPort: 80,
Protocol: "tcp",
PublishMode: swarm.PortConfigPublishModeIngress,
},
},
},
{
value: "target=80,protocol=tcp",
expected: []swarm.PortConfig{
{
Protocol: "tcp",
TargetPort: 80,
PublishMode: swarm.PortConfigPublishModeIngress,
},
},
},
{
value: "target=80,published=8080,protocol=tcp",
expected: []swarm.PortConfig{
{
Protocol: "tcp",
TargetPort: 80,
PublishedPort: 8080,
PublishMode: swarm.PortConfigPublishModeIngress,
},
},
},
{
value: "published=80,target=8080,protocol=tcp",
expected: []swarm.PortConfig{
{
Protocol: "tcp",
TargetPort: 8080,
PublishedPort: 80,
PublishMode: swarm.PortConfigPublishModeIngress,
},
},
},
{
value: "target=80,published=8080,protocol=tcp,mode=host",
expected: []swarm.PortConfig{
{
Protocol: "tcp",
TargetPort: 80,
PublishedPort: 8080,
PublishMode: "host",
},
},
},
{
value: "target=80,published=8080,mode=host",
expected: []swarm.PortConfig{
{
TargetPort: 80,
PublishedPort: 8080,
PublishMode: "host",
Protocol: "tcp",
},
},
},
{
value: "target=80,published=8080,mode=ingress",
expected: []swarm.PortConfig{
{
TargetPort: 80,
PublishedPort: 8080,
PublishMode: "ingress",
Protocol: "tcp",
},
},
},
}
for _, tc := range testCases {
var port PortOpt
assert.NoError(t, port.Set(tc.value))
assert.Len(t, port.Value(), len(tc.expected))
for _, expectedPortConfig := range tc.expected {
assertContains(t, port.Value(), expectedPortConfig)
}
}
}
func TestPortOptInvalidComplexSyntax(t *testing.T) {
testCases := []struct {
value string
expectedError string
}{
{
value: "invalid,target=80",
expectedError: "invalid field",
},
{
value: "invalid=field",
expectedError: "invalid field",
},
{
value: "protocol=invalid",
expectedError: "invalid protocol value",
},
{
value: "target=invalid",
expectedError: "invalid syntax",
},
{
value: "published=invalid",
expectedError: "invalid syntax",
},
{
value: "mode=invalid",
expectedError: "invalid publish mode value",
},
{
value: "published=8080,protocol=tcp,mode=ingress",
expectedError: "missing mandatory field",
},
{
value: `target=80,protocol="tcp,mode=ingress"`,
expectedError: "non-quoted-field",
},
{
value: `target=80,"protocol=tcp,mode=ingress"`,
expectedError: "invalid protocol value",
},
}
for _, tc := range testCases {
var port PortOpt
testutil.ErrorContains(t, port.Set(tc.value), tc.expectedError)
}
}
func TestPortOptInvalidSimpleSyntax(t *testing.T) {
testCases := []struct {
value string
expectedError string
}{
{
value: "9999999",
expectedError: "Invalid containerPort: 9999999",
},
{
value: "80/xyz",
expectedError: "Invalid proto: xyz",
},
{
value: "tcp",
expectedError: "Invalid containerPort: tcp",
},
{
value: "udp",
expectedError: "Invalid containerPort: udp",
},
{
value: "",
expectedError: "No port specified: <empty>",
},
{
value: "1.1.1.1:80:80",
expectedError: "hostip is not supported",
},
}
for _, tc := range testCases {
var port PortOpt
assert.EqualError(t, port.Set(tc.value), tc.expectedError)
}
}
func assertContains(t *testing.T, portConfigs []swarm.PortConfig, expected swarm.PortConfig) {
var contains = false
for _, portConfig := range portConfigs {
if portConfig == expected {
contains = true
break
}
}
if !contains {
t.Errorf("expected %v to contain %v, did not", portConfigs, expected)
}
}

37
opts/quotedstring.go Normal file
View File

@ -0,0 +1,37 @@
package opts
// QuotedString is a string that may have extra quotes around the value. The
// quotes are stripped from the value.
type QuotedString struct {
value *string
}
// Set sets a new value
func (s *QuotedString) Set(val string) error {
*s.value = trimQuotes(val)
return nil
}
// Type returns the type of the value
func (s *QuotedString) Type() string {
return "string"
}
func (s *QuotedString) String() string {
return string(*s.value)
}
func trimQuotes(value string) string {
lastIndex := len(value) - 1
for _, char := range []byte{'\'', '"'} {
if value[0] == char && value[lastIndex] == char {
return value[1:lastIndex]
}
}
return value
}
// NewQuotedString returns a new quoted string option
func NewQuotedString(value *string) *QuotedString {
return &QuotedString{value: value}
}

29
opts/quotedstring_test.go Normal file
View File

@ -0,0 +1,29 @@
package opts
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestQuotedStringSetWithQuotes(t *testing.T) {
value := ""
qs := NewQuotedString(&value)
assert.NoError(t, qs.Set(`"something"`))
assert.Equal(t, "something", qs.String())
assert.Equal(t, "something", value)
}
func TestQuotedStringSetWithMismatchedQuotes(t *testing.T) {
value := ""
qs := NewQuotedString(&value)
assert.NoError(t, qs.Set(`"something'`))
assert.Equal(t, `"something'`, qs.String())
}
func TestQuotedStringSetWithNoQuotes(t *testing.T) {
value := ""
qs := NewQuotedString(&value)
assert.NoError(t, qs.Set("something"))
assert.Equal(t, "something", qs.String())
}

79
opts/runtime.go Normal file
View File

@ -0,0 +1,79 @@
package opts
import (
"fmt"
"strings"
"github.com/docker/docker/api/types"
)
// RuntimeOpt defines a map of Runtimes
type RuntimeOpt struct {
name string
stockRuntimeName string
values *map[string]types.Runtime
}
// NewNamedRuntimeOpt creates a new RuntimeOpt
func NewNamedRuntimeOpt(name string, ref *map[string]types.Runtime, stockRuntime string) *RuntimeOpt {
if ref == nil {
ref = &map[string]types.Runtime{}
}
return &RuntimeOpt{name: name, values: ref, stockRuntimeName: stockRuntime}
}
// Name returns the name of the NamedListOpts in the configuration.
func (o *RuntimeOpt) Name() string {
return o.name
}
// Set validates and updates the list of Runtimes
func (o *RuntimeOpt) Set(val string) error {
parts := strings.SplitN(val, "=", 2)
if len(parts) != 2 {
return fmt.Errorf("invalid runtime argument: %s", val)
}
parts[0] = strings.TrimSpace(parts[0])
parts[1] = strings.TrimSpace(parts[1])
if parts[0] == "" || parts[1] == "" {
return fmt.Errorf("invalid runtime argument: %s", val)
}
parts[0] = strings.ToLower(parts[0])
if parts[0] == o.stockRuntimeName {
return fmt.Errorf("runtime name '%s' is reserved", o.stockRuntimeName)
}
if _, ok := (*o.values)[parts[0]]; ok {
return fmt.Errorf("runtime '%s' was already defined", parts[0])
}
(*o.values)[parts[0]] = types.Runtime{Path: parts[1]}
return nil
}
// String returns Runtime values as a string.
func (o *RuntimeOpt) String() string {
var out []string
for k := range *o.values {
out = append(out, k)
}
return fmt.Sprintf("%v", out)
}
// GetMap returns a map of Runtimes (name: path)
func (o *RuntimeOpt) GetMap() map[string]types.Runtime {
if o.values != nil {
return *o.values
}
return map[string]types.Runtime{}
}
// Type returns the type of the option
func (o *RuntimeOpt) Type() string {
return "runtime"
}

98
opts/secret.go Normal file
View File

@ -0,0 +1,98 @@
package opts
import (
"encoding/csv"
"fmt"
"os"
"strconv"
"strings"
swarmtypes "github.com/docker/docker/api/types/swarm"
)
// SecretOpt is a Value type for parsing secrets
type SecretOpt struct {
values []*swarmtypes.SecretReference
}
// Set a new secret value
func (o *SecretOpt) Set(value string) error {
csvReader := csv.NewReader(strings.NewReader(value))
fields, err := csvReader.Read()
if err != nil {
return err
}
options := &swarmtypes.SecretReference{
File: &swarmtypes.SecretReferenceFileTarget{
UID: "0",
GID: "0",
Mode: 0444,
},
}
// support a simple syntax of --secret foo
if len(fields) == 1 {
options.File.Name = fields[0]
options.SecretName = fields[0]
o.values = append(o.values, options)
return nil
}
for _, field := range fields {
parts := strings.SplitN(field, "=", 2)
key := strings.ToLower(parts[0])
if len(parts) != 2 {
return fmt.Errorf("invalid field '%s' must be a key=value pair", field)
}
value := parts[1]
switch key {
case "source", "src":
options.SecretName = value
case "target":
options.File.Name = value
case "uid":
options.File.UID = value
case "gid":
options.File.GID = value
case "mode":
m, err := strconv.ParseUint(value, 0, 32)
if err != nil {
return fmt.Errorf("invalid mode specified: %v", err)
}
options.File.Mode = os.FileMode(m)
default:
return fmt.Errorf("invalid field in secret request: %s", key)
}
}
if options.SecretName == "" {
return fmt.Errorf("source is required")
}
o.values = append(o.values, options)
return nil
}
// Type returns the type of this option
func (o *SecretOpt) Type() string {
return "secret"
}
// String returns a string repr of this option
func (o *SecretOpt) String() string {
secrets := []string{}
for _, secret := range o.values {
repr := fmt.Sprintf("%s -> %s", secret.SecretName, secret.File.Name)
secrets = append(secrets, repr)
}
return strings.Join(secrets, ", ")
}
// Value returns the secret requests
func (o *SecretOpt) Value() []*swarmtypes.SecretReference {
return o.values
}

80
opts/secret_test.go Normal file
View File

@ -0,0 +1,80 @@
package opts
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSecretOptionsSimple(t *testing.T) {
var opt SecretOpt
testCase := "app-secret"
assert.NoError(t, opt.Set(testCase))
reqs := opt.Value()
require.Len(t, reqs, 1)
req := reqs[0]
assert.Equal(t, "app-secret", req.SecretName)
assert.Equal(t, "app-secret", req.File.Name)
assert.Equal(t, "0", req.File.UID)
assert.Equal(t, "0", req.File.GID)
}
func TestSecretOptionsSourceTarget(t *testing.T) {
var opt SecretOpt
testCase := "source=foo,target=testing"
assert.NoError(t, opt.Set(testCase))
reqs := opt.Value()
require.Len(t, reqs, 1)
req := reqs[0]
assert.Equal(t, "foo", req.SecretName)
assert.Equal(t, "testing", req.File.Name)
}
func TestSecretOptionsShorthand(t *testing.T) {
var opt SecretOpt
testCase := "src=foo,target=testing"
assert.NoError(t, opt.Set(testCase))
reqs := opt.Value()
require.Len(t, reqs, 1)
req := reqs[0]
assert.Equal(t, "foo", req.SecretName)
}
func TestSecretOptionsCustomUidGid(t *testing.T) {
var opt SecretOpt
testCase := "source=foo,target=testing,uid=1000,gid=1001"
assert.NoError(t, opt.Set(testCase))
reqs := opt.Value()
require.Len(t, reqs, 1)
req := reqs[0]
assert.Equal(t, "foo", req.SecretName)
assert.Equal(t, "testing", req.File.Name)
assert.Equal(t, "1000", req.File.UID)
assert.Equal(t, "1001", req.File.GID)
}
func TestSecretOptionsCustomMode(t *testing.T) {
var opt SecretOpt
testCase := "source=foo,target=testing,uid=1000,gid=1001,mode=0444"
assert.NoError(t, opt.Set(testCase))
reqs := opt.Value()
require.Len(t, reqs, 1)
req := reqs[0]
assert.Equal(t, "foo", req.SecretName)
assert.Equal(t, "testing", req.File.Name)
assert.Equal(t, "1000", req.File.UID)
assert.Equal(t, "1001", req.File.GID)
assert.Equal(t, os.FileMode(0444), req.File.Mode)
}

111
opts/throttledevice.go Normal file
View File

@ -0,0 +1,111 @@
package opts
import (
"fmt"
"strconv"
"strings"
"github.com/docker/docker/api/types/blkiodev"
"github.com/docker/go-units"
)
// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error.
type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error)
// ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format.
func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) {
split := strings.SplitN(val, ":", 2)
if len(split) != 2 {
return nil, fmt.Errorf("bad format: %s", val)
}
if !strings.HasPrefix(split[0], "/dev/") {
return nil, fmt.Errorf("bad format for device path: %s", val)
}
rate, err := units.RAMInBytes(split[1])
if err != nil {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
}
if rate < 0 {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
}
return &blkiodev.ThrottleDevice{
Path: split[0],
Rate: uint64(rate),
}, nil
}
// ValidateThrottleIOpsDevice validates that the specified string has a valid device-rate format.
func ValidateThrottleIOpsDevice(val string) (*blkiodev.ThrottleDevice, error) {
split := strings.SplitN(val, ":", 2)
if len(split) != 2 {
return nil, fmt.Errorf("bad format: %s", val)
}
if !strings.HasPrefix(split[0], "/dev/") {
return nil, fmt.Errorf("bad format for device path: %s", val)
}
rate, err := strconv.ParseUint(split[1], 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
}
if rate < 0 {
return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
}
return &blkiodev.ThrottleDevice{
Path: split[0],
Rate: uint64(rate),
}, nil
}
// ThrottledeviceOpt defines a map of ThrottleDevices
type ThrottledeviceOpt struct {
values []*blkiodev.ThrottleDevice
validator ValidatorThrottleFctType
}
// NewThrottledeviceOpt creates a new ThrottledeviceOpt
func NewThrottledeviceOpt(validator ValidatorThrottleFctType) ThrottledeviceOpt {
values := []*blkiodev.ThrottleDevice{}
return ThrottledeviceOpt{
values: values,
validator: validator,
}
}
// Set validates a ThrottleDevice and sets its name as a key in ThrottledeviceOpt
func (opt *ThrottledeviceOpt) Set(val string) error {
var value *blkiodev.ThrottleDevice
if opt.validator != nil {
v, err := opt.validator(val)
if err != nil {
return err
}
value = v
}
(opt.values) = append((opt.values), value)
return nil
}
// String returns ThrottledeviceOpt values as a string.
func (opt *ThrottledeviceOpt) String() string {
var out []string
for _, v := range opt.values {
out = append(out, v.String())
}
return fmt.Sprintf("%v", out)
}
// GetList returns a slice of pointers to ThrottleDevices.
func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice {
var throttledevice []*blkiodev.ThrottleDevice
throttledevice = append(throttledevice, opt.values...)
return throttledevice
}
// Type returns the option type
func (opt *ThrottledeviceOpt) Type() string {
return "list"
}

57
opts/ulimit.go Normal file
View File

@ -0,0 +1,57 @@
package opts
import (
"fmt"
"github.com/docker/go-units"
)
// UlimitOpt defines a map of Ulimits
type UlimitOpt struct {
values *map[string]*units.Ulimit
}
// NewUlimitOpt creates a new UlimitOpt
func NewUlimitOpt(ref *map[string]*units.Ulimit) *UlimitOpt {
if ref == nil {
ref = &map[string]*units.Ulimit{}
}
return &UlimitOpt{ref}
}
// Set validates a Ulimit and sets its name as a key in UlimitOpt
func (o *UlimitOpt) Set(val string) error {
l, err := units.ParseUlimit(val)
if err != nil {
return err
}
(*o.values)[l.Name] = l
return nil
}
// String returns Ulimit values as a string.
func (o *UlimitOpt) String() string {
var out []string
for _, v := range *o.values {
out = append(out, v.String())
}
return fmt.Sprintf("%v", out)
}
// GetList returns a slice of pointers to Ulimits.
func (o *UlimitOpt) GetList() []*units.Ulimit {
var ulimits []*units.Ulimit
for _, v := range *o.values {
ulimits = append(ulimits, v)
}
return ulimits
}
// Type returns the option type
func (o *UlimitOpt) Type() string {
return "ulimit"
}

42
opts/ulimit_test.go Normal file
View File

@ -0,0 +1,42 @@
package opts
import (
"testing"
"github.com/docker/go-units"
)
func TestUlimitOpt(t *testing.T) {
ulimitMap := map[string]*units.Ulimit{
"nofile": {"nofile", 1024, 512},
}
ulimitOpt := NewUlimitOpt(&ulimitMap)
expected := "[nofile=512:1024]"
if ulimitOpt.String() != expected {
t.Fatalf("Expected %v, got %v", expected, ulimitOpt)
}
// Valid ulimit append to opts
if err := ulimitOpt.Set("core=1024:1024"); err != nil {
t.Fatal(err)
}
// Invalid ulimit type returns an error and do not append to opts
if err := ulimitOpt.Set("notavalidtype=1024:1024"); err == nil {
t.Fatalf("Expected error on invalid ulimit type")
}
expected = "[nofile=512:1024 core=1024:1024]"
expected2 := "[core=1024:1024 nofile=512:1024]"
result := ulimitOpt.String()
if result != expected && result != expected2 {
t.Fatalf("Expected %v or %v, got %v", expected, expected2, ulimitOpt)
}
// And test GetList
ulimits := ulimitOpt.GetList()
if len(ulimits) != 2 {
t.Fatalf("Expected a ulimit list of 2, got %v", ulimits)
}
}

89
opts/weightdevice.go Normal file
View File

@ -0,0 +1,89 @@
package opts
import (
"fmt"
"strconv"
"strings"
"github.com/docker/docker/api/types/blkiodev"
)
// ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error.
type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error)
// ValidateWeightDevice validates that the specified string has a valid device-weight format.
func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) {
split := strings.SplitN(val, ":", 2)
if len(split) != 2 {
return nil, fmt.Errorf("bad format: %s", val)
}
if !strings.HasPrefix(split[0], "/dev/") {
return nil, fmt.Errorf("bad format for device path: %s", val)
}
weight, err := strconv.ParseUint(split[1], 10, 0)
if err != nil {
return nil, fmt.Errorf("invalid weight for device: %s", val)
}
if weight > 0 && (weight < 10 || weight > 1000) {
return nil, fmt.Errorf("invalid weight for device: %s", val)
}
return &blkiodev.WeightDevice{
Path: split[0],
Weight: uint16(weight),
}, nil
}
// WeightdeviceOpt defines a map of WeightDevices
type WeightdeviceOpt struct {
values []*blkiodev.WeightDevice
validator ValidatorWeightFctType
}
// NewWeightdeviceOpt creates a new WeightdeviceOpt
func NewWeightdeviceOpt(validator ValidatorWeightFctType) WeightdeviceOpt {
values := []*blkiodev.WeightDevice{}
return WeightdeviceOpt{
values: values,
validator: validator,
}
}
// Set validates a WeightDevice and sets its name as a key in WeightdeviceOpt
func (opt *WeightdeviceOpt) Set(val string) error {
var value *blkiodev.WeightDevice
if opt.validator != nil {
v, err := opt.validator(val)
if err != nil {
return err
}
value = v
}
(opt.values) = append((opt.values), value)
return nil
}
// String returns WeightdeviceOpt values as a string.
func (opt *WeightdeviceOpt) String() string {
var out []string
for _, v := range opt.values {
out = append(out, v.String())
}
return fmt.Sprintf("%v", out)
}
// GetList returns a slice of pointers to WeightDevices.
func (opt *WeightdeviceOpt) GetList() []*blkiodev.WeightDevice {
var weightdevice []*blkiodev.WeightDevice
for _, v := range opt.values {
weightdevice = append(weightdevice, v)
}
return weightdevice
}
// Type returns the option type
func (opt *WeightdeviceOpt) Type() string {
return "list"
}