mirror of https://github.com/docker/cli.git
Merge pull request #82 from vdemeester/opts-from-docker
Import `opts` package from moby/moby
This commit is contained in:
commit
36e557f1a3
|
@ -12,11 +12,11 @@ import (
|
||||||
"github.com/docker/cli/cli/config/configfile"
|
"github.com/docker/cli/cli/config/configfile"
|
||||||
"github.com/docker/cli/cli/config/credentials"
|
"github.com/docker/cli/cli/config/credentials"
|
||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
|
dopts "github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
dopts "github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/go-connections/sockets"
|
"github.com/docker/go-connections/sockets"
|
||||||
"github.com/docker/go-connections/tlsconfig"
|
"github.com/docker/go-connections/tlsconfig"
|
||||||
"github.com/docker/notary/passphrase"
|
"github.com/docker/notary/passphrase"
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +17,7 @@ type listOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
|
func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := listOptions{filter: opts.NewFilterOpt()}
|
listOpts := listOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls [OPTIONS]",
|
Use: "ls [OPTIONS]",
|
||||||
|
@ -25,30 +25,30 @@ func newConfigListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
Short: "List configs",
|
Short: "List configs",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runConfigList(dockerCli, opts)
|
return runConfigList(dockerCli, listOpts)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
flags.BoolVarP(&listOpts.quiet, "quiet", "q", false, "Only display IDs")
|
||||||
flags.StringVarP(&opts.format, "format", "", "", "Pretty-print configs using a Go template")
|
flags.StringVarP(&listOpts.format, "format", "", "", "Pretty-print configs using a Go template")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&listOpts.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runConfigList(dockerCli command.Cli, opts listOptions) error {
|
func runConfigList(dockerCli command.Cli, options listOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().ConfigFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().ConfigFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().ConfigFormat
|
format = dockerCli.ConfigFile().ConfigFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -57,7 +57,7 @@ func runConfigList(dockerCli command.Cli, opts listOptions) error {
|
||||||
|
|
||||||
configCtx := formatter.Context{
|
configCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewConfigFormat(format, opts.quiet),
|
Format: formatter.NewConfigFormat(format, options.quiet),
|
||||||
}
|
}
|
||||||
return formatter.ConfigWrite(configCtx, configs)
|
return formatter.ConfigWrite(configCtx, configs)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
dockeropts "github.com/docker/docker/opts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -18,54 +18,54 @@ type commitOptions struct {
|
||||||
pause bool
|
pause bool
|
||||||
comment string
|
comment string
|
||||||
author string
|
author string
|
||||||
changes dockeropts.ListOpts
|
changes opts.ListOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCommitCommand creates a new cobra.Command for `docker commit`
|
// NewCommitCommand creates a new cobra.Command for `docker commit`
|
||||||
func NewCommitCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewCommitCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
var opts commitOptions
|
var options commitOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]",
|
Use: "commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]",
|
||||||
Short: "Create a new image from a container's changes",
|
Short: "Create a new image from a container's changes",
|
||||||
Args: cli.RequiresRangeArgs(1, 2),
|
Args: cli.RequiresRangeArgs(1, 2),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.container = args[0]
|
options.container = args[0]
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
opts.reference = args[1]
|
options.reference = args[1]
|
||||||
}
|
}
|
||||||
return runCommit(dockerCli, &opts)
|
return runCommit(dockerCli, &options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.SetInterspersed(false)
|
flags.SetInterspersed(false)
|
||||||
|
|
||||||
flags.BoolVarP(&opts.pause, "pause", "p", true, "Pause container during commit")
|
flags.BoolVarP(&options.pause, "pause", "p", true, "Pause container during commit")
|
||||||
flags.StringVarP(&opts.comment, "message", "m", "", "Commit message")
|
flags.StringVarP(&options.comment, "message", "m", "", "Commit message")
|
||||||
flags.StringVarP(&opts.author, "author", "a", "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
|
flags.StringVarP(&options.author, "author", "a", "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
|
||||||
|
|
||||||
opts.changes = dockeropts.NewListOpts(nil)
|
options.changes = opts.NewListOpts(nil)
|
||||||
flags.VarP(&opts.changes, "change", "c", "Apply Dockerfile instruction to the created image")
|
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCommit(dockerCli *command.DockerCli, opts *commitOptions) error {
|
func runCommit(dockerCli *command.DockerCli, options *commitOptions) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
name := opts.container
|
name := options.container
|
||||||
reference := opts.reference
|
reference := options.reference
|
||||||
|
|
||||||
options := types.ContainerCommitOptions{
|
commitOptions := types.ContainerCommitOptions{
|
||||||
Reference: reference,
|
Reference: reference,
|
||||||
Comment: opts.comment,
|
Comment: options.comment,
|
||||||
Author: opts.author,
|
Author: options.author,
|
||||||
Changes: opts.changes.GetAll(),
|
Changes: options.changes.GetAll(),
|
||||||
Pause: opts.pause,
|
Pause: options.pause,
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := dockerCli.Client().ContainerCommit(ctx, name, options)
|
response, err := dockerCli.Client().ContainerCommit(ctx, name, commitOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
apiclient "github.com/docker/docker/client"
|
apiclient "github.com/docker/docker/client"
|
||||||
options "github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/promise"
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -22,19 +22,19 @@ type execOptions struct {
|
||||||
detach bool
|
detach bool
|
||||||
user string
|
user string
|
||||||
privileged bool
|
privileged bool
|
||||||
env *options.ListOpts
|
env *opts.ListOpts
|
||||||
}
|
}
|
||||||
|
|
||||||
func newExecOptions() *execOptions {
|
func newExecOptions() *execOptions {
|
||||||
var values []string
|
var values []string
|
||||||
return &execOptions{
|
return &execOptions{
|
||||||
env: options.NewListOptsRef(&values, options.ValidateEnv),
|
env: opts.NewListOptsRef(&values, opts.ValidateEnv),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewExecCommand creates a new cobra.Command for `docker exec`
|
// NewExecCommand creates a new cobra.Command for `docker exec`
|
||||||
func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
opts := newExecOptions()
|
options := newExecOptions()
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "exec [OPTIONS] CONTAINER COMMAND [ARG...]",
|
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 {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
container := args[0]
|
container := args[0]
|
||||||
execCmd := args[1:]
|
execCmd := args[1:]
|
||||||
return runExec(dockerCli, opts, container, execCmd)
|
return runExec(dockerCli, options, container, execCmd)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.SetInterspersed(false)
|
flags.SetInterspersed(false)
|
||||||
|
|
||||||
flags.StringVarP(&opts.detachKeys, "detach-keys", "", "", "Override the key sequence for detaching a container")
|
flags.StringVarP(&options.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(&options.interactive, "interactive", "i", false, "Keep STDIN open even if not attached")
|
||||||
flags.BoolVarP(&opts.tty, "tty", "t", false, "Allocate a pseudo-TTY")
|
flags.BoolVarP(&options.tty, "tty", "t", false, "Allocate a pseudo-TTY")
|
||||||
flags.BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: run command in the background")
|
flags.BoolVarP(&options.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.StringVarP(&options.user, "user", "u", "", "Username or UID (format: <name|uid>[:<group|gid>])")
|
||||||
flags.BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the command")
|
flags.BoolVarP(&options.privileged, "privileged", "", false, "Give extended privileges to the command")
|
||||||
flags.VarP(opts.env, "env", "e", "Set environment variables")
|
flags.VarP(options.env, "env", "e", "Set environment variables")
|
||||||
flags.SetAnnotation("env", "version", []string{"1.25"})
|
flags.SetAnnotation("env", "version", []string{"1.25"})
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
func runExec(dockerCli *command.DockerCli, opts *execOptions, container string, execCmd []string) error {
|
func runExec(dockerCli *command.DockerCli, options *execOptions, container string, execCmd []string) error {
|
||||||
execConfig, err := parseExec(opts, execCmd)
|
execConfig, err := parseExec(options, execCmd)
|
||||||
// just in case the ParseExec does not exit
|
// just in case the ParseExec does not exit
|
||||||
if container == "" || err != nil {
|
if container == "" || err != nil {
|
||||||
return cli.StatusError{StatusCode: 1}
|
return cli.StatusError{StatusCode: 1}
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.detachKeys != "" {
|
if options.detachKeys != "" {
|
||||||
dockerCli.ConfigFile().DetachKeys = opts.detachKeys
|
dockerCli.ConfigFile().DetachKeys = options.detachKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send client escape keys
|
// Send client escape keys
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/templates"
|
"github.com/docker/docker/pkg/templates"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -26,27 +26,27 @@ type psOptions struct {
|
||||||
|
|
||||||
// NewPsCommand creates a new cobra.Command for `docker ps`
|
// NewPsCommand creates a new cobra.Command for `docker ps`
|
||||||
func NewPsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewPsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
opts := psOptions{filter: opts.NewFilterOpt()}
|
options := psOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ps [OPTIONS]",
|
Use: "ps [OPTIONS]",
|
||||||
Short: "List containers",
|
Short: "List containers",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runPs(dockerCli, &opts)
|
return runPs(dockerCli, &options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display numeric IDs")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display numeric IDs")
|
||||||
flags.BoolVarP(&opts.size, "size", "s", false, "Display total file sizes")
|
flags.BoolVarP(&options.size, "size", "s", false, "Display total file sizes")
|
||||||
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
|
flags.BoolVarP(&options.all, "all", "a", false, "Show all containers (default shows just running)")
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||||
flags.BoolVarP(&opts.nLatest, "latest", "l", false, "Show the latest created container (includes all states)")
|
flags.BoolVarP(&options.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.IntVarP(&options.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.StringVarP(&options.format, "format", "", "", "Pretty-print containers using a Go template")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -109,10 +109,10 @@ func buildContainerListOptions(opts *psOptions) (*types.ContainerListOptions, er
|
||||||
return options, nil
|
return options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPs(dockerCli *command.DockerCli, opts *psOptions) error {
|
func runPs(dockerCli *command.DockerCli, options *psOptions) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
listOptions, err := buildContainerListOptions(opts)
|
listOptions, err := buildContainerListOptions(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -122,9 +122,9 @@ func runPs(dockerCli *command.DockerCli, opts *psOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().PsFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().PsFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().PsFormat
|
format = dockerCli.ConfigFile().PsFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -133,8 +133,8 @@ func runPs(dockerCli *command.DockerCli, opts *psOptions) error {
|
||||||
|
|
||||||
containerCtx := formatter.Context{
|
containerCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewContainerFormat(format, opts.quiet, listOptions.Size),
|
Format: formatter.NewContainerFormat(format, options.quiet, listOptions.Size),
|
||||||
Trunc: !opts.noTrunc,
|
Trunc: !options.noTrunc,
|
||||||
}
|
}
|
||||||
return formatter.ContainerWrite(containerCtx, containers)
|
return formatter.ContainerWrite(containerCtx, containers)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,10 +12,10 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
networktypes "github.com/docker/docker/api/types/network"
|
networktypes "github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/strslice"
|
"github.com/docker/docker/api/types/strslice"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/signal"
|
"github.com/docker/docker/pkg/signal"
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/cli/opts"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -18,14 +18,14 @@ type pruneOptions struct {
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for containers
|
// NewPruneCommand returns a new cobra prune command for containers
|
||||||
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
options := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune [OPTIONS]",
|
Use: "prune [OPTIONS]",
|
||||||
Short: "Remove all stopped containers",
|
Short: "Remove all stopped containers",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
spaceReclaimed, output, err := runPrune(dockerCli, opts)
|
spaceReclaimed, output, err := runPrune(dockerCli, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,8 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
|
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
|
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -48,10 +48,10 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
const warning = `WARNING! This will remove all stopped containers.
|
const warning = `WARNING! This will remove all stopped containers.
|
||||||
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) {
|
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||||
pruneFilters := command.PruneFilters(dockerCli, opts.filter.Value())
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package container
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
containertypes "github.com/docker/docker/api/types/container"
|
containertypes "github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -37,71 +37,71 @@ type updateOptions struct {
|
||||||
|
|
||||||
// NewUpdateCommand creates a new cobra.Command for `docker update`
|
// NewUpdateCommand creates a new cobra.Command for `docker update`
|
||||||
func NewUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
var opts updateOptions
|
var options updateOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "update [OPTIONS] CONTAINER [CONTAINER...]",
|
Use: "update [OPTIONS] CONTAINER [CONTAINER...]",
|
||||||
Short: "Update configuration of one or more containers",
|
Short: "Update configuration of one or more containers",
|
||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.containers = args
|
options.containers = args
|
||||||
opts.nFlag = cmd.Flags().NFlag()
|
options.nFlag = cmd.Flags().NFlag()
|
||||||
return runUpdate(dockerCli, &opts)
|
return runUpdate(dockerCli, &options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
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.Uint16Var(&options.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(&options.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(&options.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.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.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.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(&options.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.StringVar(&options.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.Int64VarP(&options.cpuShares, "cpu-shares", "c", 0, "CPU shares (relative weight)")
|
||||||
flags.VarP(&opts.memory, "memory", "m", "Memory limit")
|
flags.VarP(&options.memory, "memory", "m", "Memory limit")
|
||||||
flags.Var(&opts.memoryReservation, "memory-reservation", "Memory soft limit")
|
flags.Var(&options.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(&options.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.Var(&options.kernelMemory, "kernel-memory", "Kernel memory limit")
|
||||||
flags.StringVar(&opts.restartPolicy, "restart", "", "Restart policy to apply when a container exits")
|
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"})
|
flags.SetAnnotation("cpus", "version", []string{"1.29"})
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runUpdate(dockerCli *command.DockerCli, opts *updateOptions) error {
|
func runUpdate(dockerCli *command.DockerCli, options *updateOptions) error {
|
||||||
var err 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")
|
return errors.New("you must provide one or more flags when using this command")
|
||||||
}
|
}
|
||||||
|
|
||||||
var restartPolicy containertypes.RestartPolicy
|
var restartPolicy containertypes.RestartPolicy
|
||||||
if opts.restartPolicy != "" {
|
if options.restartPolicy != "" {
|
||||||
restartPolicy, err = runconfigopts.ParseRestartPolicy(opts.restartPolicy)
|
restartPolicy, err = runconfigopts.ParseRestartPolicy(options.restartPolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resources := containertypes.Resources{
|
resources := containertypes.Resources{
|
||||||
BlkioWeight: opts.blkioWeight,
|
BlkioWeight: options.blkioWeight,
|
||||||
CpusetCpus: opts.cpusetCpus,
|
CpusetCpus: options.cpusetCpus,
|
||||||
CpusetMems: opts.cpusetMems,
|
CpusetMems: options.cpusetMems,
|
||||||
CPUShares: opts.cpuShares,
|
CPUShares: options.cpuShares,
|
||||||
Memory: opts.memory.Value(),
|
Memory: options.memory.Value(),
|
||||||
MemoryReservation: opts.memoryReservation.Value(),
|
MemoryReservation: options.memoryReservation.Value(),
|
||||||
MemorySwap: opts.memorySwap.Value(),
|
MemorySwap: options.memorySwap.Value(),
|
||||||
KernelMemory: opts.kernelMemory.Value(),
|
KernelMemory: options.kernelMemory.Value(),
|
||||||
CPUPeriod: opts.cpuPeriod,
|
CPUPeriod: options.cpuPeriod,
|
||||||
CPUQuota: opts.cpuQuota,
|
CPUQuota: options.cpuQuota,
|
||||||
CPURealtimePeriod: opts.cpuRealtimePeriod,
|
CPURealtimePeriod: options.cpuRealtimePeriod,
|
||||||
CPURealtimeRuntime: opts.cpuRealtimeRuntime,
|
CPURealtimeRuntime: options.cpuRealtimeRuntime,
|
||||||
NanoCPUs: opts.cpus.Value(),
|
NanoCPUs: options.cpus.Value(),
|
||||||
}
|
}
|
||||||
|
|
||||||
updateConfig := containertypes.UpdateConfig{
|
updateConfig := containertypes.UpdateConfig{
|
||||||
|
@ -115,7 +115,7 @@ func runUpdate(dockerCli *command.DockerCli, opts *updateOptions) error {
|
||||||
warns []string
|
warns []string
|
||||||
errs []string
|
errs []string
|
||||||
)
|
)
|
||||||
for _, container := range opts.containers {
|
for _, container := range options.containers {
|
||||||
r, err := dockerCli.Client().ContainerUpdate(ctx, container, updateConfig)
|
r, err := dockerCli.Client().ContainerUpdate(ctx, container, updateConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err.Error())
|
errs = append(errs, err.Error())
|
||||||
|
|
|
@ -15,11 +15,11 @@ import (
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/image/build"
|
"github.com/docker/cli/cli/command/image/build"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/docker/distribution/reference"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
"github.com/docker/docker/pkg/progress"
|
"github.com/docker/docker/pkg/progress"
|
||||||
|
|
|
@ -4,15 +4,14 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
dockeropts "github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
dockeropts "github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type importOptions struct {
|
type importOptions struct {
|
||||||
|
@ -24,41 +23,41 @@ type importOptions struct {
|
||||||
|
|
||||||
// NewImportCommand creates a new `docker import` command
|
// NewImportCommand creates a new `docker import` command
|
||||||
func NewImportCommand(dockerCli command.Cli) *cobra.Command {
|
func NewImportCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
var opts importOptions
|
var options importOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]",
|
Use: "import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]",
|
||||||
Short: "Import the contents from a tarball to create a filesystem image",
|
Short: "Import the contents from a tarball to create a filesystem image",
|
||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.source = args[0]
|
options.source = args[0]
|
||||||
if len(args) > 1 {
|
if len(args) > 1 {
|
||||||
opts.reference = args[1]
|
options.reference = args[1]
|
||||||
}
|
}
|
||||||
return runImport(dockerCli, opts)
|
return runImport(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
opts.changes = dockeropts.NewListOpts(nil)
|
options.changes = dockeropts.NewListOpts(nil)
|
||||||
flags.VarP(&opts.changes, "change", "c", "Apply Dockerfile instruction to the created image")
|
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image")
|
||||||
flags.StringVarP(&opts.message, "message", "m", "", "Set commit message for imported image")
|
flags.StringVarP(&options.message, "message", "m", "", "Set commit message for imported image")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runImport(dockerCli command.Cli, opts importOptions) error {
|
func runImport(dockerCli command.Cli, options importOptions) error {
|
||||||
var (
|
var (
|
||||||
in io.Reader
|
in io.Reader
|
||||||
srcName = opts.source
|
srcName = options.source
|
||||||
)
|
)
|
||||||
|
|
||||||
if opts.source == "-" {
|
if options.source == "-" {
|
||||||
in = dockerCli.In()
|
in = dockerCli.In()
|
||||||
} else if !urlutil.IsURL(opts.source) {
|
} else if !urlutil.IsURL(options.source) {
|
||||||
srcName = "-"
|
srcName = "-"
|
||||||
file, err := os.Open(opts.source)
|
file, err := os.Open(options.source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -71,14 +70,14 @@ func runImport(dockerCli command.Cli, opts importOptions) error {
|
||||||
SourceName: srcName,
|
SourceName: srcName,
|
||||||
}
|
}
|
||||||
|
|
||||||
options := types.ImageImportOptions{
|
importOptions := types.ImageImportOptions{
|
||||||
Message: opts.message,
|
Message: options.message,
|
||||||
Changes: opts.changes.GetAll(),
|
Changes: options.changes.GetAll(),
|
||||||
}
|
}
|
||||||
|
|
||||||
clnt := dockerCli.Client()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
package image
|
package image
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type imagesOptions struct {
|
type imagesOptions struct {
|
||||||
|
@ -24,7 +23,7 @@ type imagesOptions struct {
|
||||||
|
|
||||||
// NewImagesCommand creates a new `docker images` command
|
// NewImagesCommand creates a new `docker images` command
|
||||||
func NewImagesCommand(dockerCli command.Cli) *cobra.Command {
|
func NewImagesCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := imagesOptions{filter: opts.NewFilterOpt()}
|
options := imagesOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "images [OPTIONS] [REPOSITORY[:TAG]]",
|
Use: "images [OPTIONS] [REPOSITORY[:TAG]]",
|
||||||
|
@ -32,20 +31,20 @@ func NewImagesCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
Args: cli.RequiresMaxArgs(1),
|
Args: cli.RequiresMaxArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
opts.matchName = args[0]
|
options.matchName = args[0]
|
||||||
}
|
}
|
||||||
return runImages(dockerCli, opts)
|
return runImages(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only show numeric IDs")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only show numeric IDs")
|
||||||
flags.BoolVarP(&opts.all, "all", "a", false, "Show all images (default hides intermediate images)")
|
flags.BoolVarP(&options.all, "all", "a", false, "Show all images (default hides intermediate images)")
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||||
flags.BoolVar(&opts.showDigests, "digests", false, "Show digests")
|
flags.BoolVar(&options.showDigests, "digests", false, "Show digests")
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print images using a Go template")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -57,27 +56,27 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
return &cmd
|
return &cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runImages(dockerCli command.Cli, opts imagesOptions) error {
|
func runImages(dockerCli command.Cli, options imagesOptions) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
filters := opts.filter.Value()
|
filters := options.filter.Value()
|
||||||
if opts.matchName != "" {
|
if options.matchName != "" {
|
||||||
filters.Add("reference", opts.matchName)
|
filters.Add("reference", options.matchName)
|
||||||
}
|
}
|
||||||
|
|
||||||
options := types.ImageListOptions{
|
listOptions := types.ImageListOptions{
|
||||||
All: opts.all,
|
All: options.all,
|
||||||
Filters: filters,
|
Filters: filters,
|
||||||
}
|
}
|
||||||
|
|
||||||
images, err := dockerCli.Client().ImageList(ctx, options)
|
images, err := dockerCli.Client().ImageList(ctx, listOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().ImagesFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().ImagesFormat
|
format = dockerCli.ConfigFile().ImagesFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -87,10 +86,10 @@ func runImages(dockerCli command.Cli, opts imagesOptions) error {
|
||||||
imageCtx := formatter.ImageContext{
|
imageCtx := formatter.ImageContext{
|
||||||
Context: formatter.Context{
|
Context: formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewImageFormat(format, opts.quiet, opts.showDigests),
|
Format: formatter.NewImageFormat(format, options.quiet, options.showDigests),
|
||||||
Trunc: !opts.noTrunc,
|
Trunc: !options.noTrunc,
|
||||||
},
|
},
|
||||||
Digest: opts.showDigests,
|
Digest: options.showDigests,
|
||||||
}
|
}
|
||||||
return formatter.ImageWrite(imageCtx, images)
|
return formatter.ImageWrite(imageCtx, images)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/cli/opts"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -20,14 +20,14 @@ type pruneOptions struct {
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for images
|
// NewPruneCommand returns a new cobra prune command for images
|
||||||
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
options := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune [OPTIONS]",
|
Use: "prune [OPTIONS]",
|
||||||
Short: "Remove unused images",
|
Short: "Remove unused images",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
spaceReclaimed, output, err := runPrune(dockerCli, opts)
|
spaceReclaimed, output, err := runPrune(dockerCli, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -41,9 +41,9 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
|
flags.BoolVarP(&options.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.BoolVarP(&options.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.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
@ -55,16 +55,16 @@ Are you sure you want to continue?`
|
||||||
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) {
|
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||||
pruneFilters := opts.filter.Value()
|
pruneFilters := options.filter.Value()
|
||||||
pruneFilters.Add("dangling", fmt.Sprintf("%v", !opts.all))
|
pruneFilters.Add("dangling", fmt.Sprintf("%v", !options.all))
|
||||||
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
||||||
|
|
||||||
warning := danglingWarning
|
warning := danglingWarning
|
||||||
if opts.all {
|
if options.all {
|
||||||
warning = allImageWarning
|
warning = allImageWarning
|
||||||
}
|
}
|
||||||
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type connectOptions struct {
|
type connectOptions struct {
|
||||||
|
@ -21,7 +20,7 @@ type connectOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
opts := connectOptions{
|
options := connectOptions{
|
||||||
links: opts.NewListOpts(opts.ValidateLink),
|
links: opts.NewListOpts(opts.ValidateLink),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,34 +29,34 @@ func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
Short: "Connect a container to a network",
|
Short: "Connect a container to a network",
|
||||||
Args: cli.ExactArgs(2),
|
Args: cli.ExactArgs(2),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.network = args[0]
|
options.network = args[0]
|
||||||
opts.container = args[1]
|
options.container = args[1]
|
||||||
return runConnect(dockerCli, opts)
|
return runConnect(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVar(&opts.ipaddress, "ip", "", "IPv4 address (e.g., 172.30.100.104)")
|
flags.StringVar(&options.ipaddress, "ip", "", "IPv4 address (e.g., 172.30.100.104)")
|
||||||
flags.StringVar(&opts.ipv6address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)")
|
flags.StringVar(&options.ipv6address, "ip6", "", "IPv6 address (e.g., 2001:db8::33)")
|
||||||
flags.Var(&opts.links, "link", "Add link to another container")
|
flags.Var(&options.links, "link", "Add link to another container")
|
||||||
flags.StringSliceVar(&opts.aliases, "alias", []string{}, "Add network-scoped alias for the container")
|
flags.StringSliceVar(&options.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.StringSliceVar(&options.linklocalips, "link-local-ip", []string{}, "Add a link-local address for the container")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runConnect(dockerCli *command.DockerCli, opts connectOptions) error {
|
func runConnect(dockerCli *command.DockerCli, options connectOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
|
|
||||||
epConfig := &network.EndpointSettings{
|
epConfig := &network.EndpointSettings{
|
||||||
IPAMConfig: &network.EndpointIPAMConfig{
|
IPAMConfig: &network.EndpointIPAMConfig{
|
||||||
IPv4Address: opts.ipaddress,
|
IPv4Address: options.ipaddress,
|
||||||
IPv6Address: opts.ipv6address,
|
IPv6Address: options.ipv6address,
|
||||||
LinkLocalIPs: opts.linklocalips,
|
LinkLocalIPs: options.linklocalips,
|
||||||
},
|
},
|
||||||
Links: opts.links.GetAll(),
|
Links: options.links.GetAll(),
|
||||||
Aliases: opts.aliases,
|
Aliases: options.aliases,
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.NetworkConnect(context.Background(), opts.network, opts.container, epConfig)
|
return client.NetworkConnect(context.Background(), options.network, options.container, epConfig)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,16 +5,15 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type createOptions struct {
|
type createOptions struct {
|
||||||
|
@ -36,7 +35,7 @@ type createOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
opts := createOptions{
|
options := createOptions{
|
||||||
driverOpts: *opts.NewMapOpts(nil, nil),
|
driverOpts: *opts.NewMapOpts(nil, nil),
|
||||||
labels: opts.NewListOpts(opts.ValidateEnv),
|
labels: opts.NewListOpts(opts.ValidateEnv),
|
||||||
ipamAux: *opts.NewMapOpts(nil, nil),
|
ipamAux: *opts.NewMapOpts(nil, nil),
|
||||||
|
@ -48,59 +47,59 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
Short: "Create a network",
|
Short: "Create a network",
|
||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.name = args[0]
|
options.name = args[0]
|
||||||
return runCreate(dockerCli, opts)
|
return runCreate(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVarP(&opts.driver, "driver", "d", "bridge", "Driver to manage the Network")
|
flags.StringVarP(&options.driver, "driver", "d", "bridge", "Driver to manage the Network")
|
||||||
flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options")
|
flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options")
|
||||||
flags.Var(&opts.labels, "label", "Set metadata on a network")
|
flags.Var(&options.labels, "label", "Set metadata on a network")
|
||||||
flags.BoolVar(&opts.internal, "internal", false, "Restrict external access to the network")
|
flags.BoolVar(&options.internal, "internal", false, "Restrict external access to the network")
|
||||||
flags.BoolVar(&opts.ipv6, "ipv6", false, "Enable IPv6 networking")
|
flags.BoolVar(&options.ipv6, "ipv6", false, "Enable IPv6 networking")
|
||||||
flags.BoolVar(&opts.attachable, "attachable", false, "Enable manual container attachment")
|
flags.BoolVar(&options.attachable, "attachable", false, "Enable manual container attachment")
|
||||||
flags.SetAnnotation("attachable", "version", []string{"1.25"})
|
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.SetAnnotation("ingress", "version", []string{"1.29"})
|
||||||
|
|
||||||
flags.StringVar(&opts.ipamDriver, "ipam-driver", "default", "IP Address Management Driver")
|
flags.StringVar(&options.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(&options.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(&options.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.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(&options.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.ipamOpt, "ipam-opt", "Set IPAM driver specific options")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCreate(dockerCli *command.DockerCli, opts createOptions) error {
|
func runCreate(dockerCli *command.DockerCli, options createOptions) error {
|
||||||
client := dockerCli.Client()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct network create request body
|
// Construct network create request body
|
||||||
nc := types.NetworkCreate{
|
nc := types.NetworkCreate{
|
||||||
Driver: opts.driver,
|
Driver: options.driver,
|
||||||
Options: opts.driverOpts.GetAll(),
|
Options: options.driverOpts.GetAll(),
|
||||||
IPAM: &network.IPAM{
|
IPAM: &network.IPAM{
|
||||||
Driver: opts.ipamDriver,
|
Driver: options.ipamDriver,
|
||||||
Config: ipamCfg,
|
Config: ipamCfg,
|
||||||
Options: opts.ipamOpt.GetAll(),
|
Options: options.ipamOpt.GetAll(),
|
||||||
},
|
},
|
||||||
CheckDuplicate: true,
|
CheckDuplicate: true,
|
||||||
Internal: opts.internal,
|
Internal: options.internal,
|
||||||
EnableIPv6: opts.ipv6,
|
EnableIPv6: options.ipv6,
|
||||||
Attachable: opts.attachable,
|
Attachable: options.attachable,
|
||||||
Ingress: opts.ingress,
|
Ingress: options.ingress,
|
||||||
Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,14 +3,13 @@ package network
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type byNetworkName []types.NetworkResource
|
type byNetworkName []types.NetworkResource
|
||||||
|
@ -27,7 +26,7 @@ type listOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
opts := listOptions{filter: opts.NewFilterOpt()}
|
options := listOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls [OPTIONS]",
|
Use: "ls [OPTIONS]",
|
||||||
|
@ -35,30 +34,30 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
Short: "List networks",
|
Short: "List networks",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runList(dockerCli, opts)
|
return runList(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display network IDs")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display network IDs")
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output")
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate the output")
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print networks using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print networks using a Go template")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Provide filter values (e.g. 'driver=bridge')")
|
flags.VarP(&options.filter, "filter", "f", "Provide filter values (e.g. 'driver=bridge')")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
func runList(dockerCli *command.DockerCli, options listOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
options := types.NetworkListOptions{Filters: opts.filter.Value()}
|
listOptions := types.NetworkListOptions{Filters: options.filter.Value()}
|
||||||
networkResources, err := client.NetworkList(context.Background(), options)
|
networkResources, err := client.NetworkList(context.Background(), listOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().NetworksFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().NetworksFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().NetworksFormat
|
format = dockerCli.ConfigFile().NetworksFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -69,8 +68,8 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
||||||
|
|
||||||
networksCtx := formatter.Context{
|
networksCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewNetworkFormat(format, opts.quiet),
|
Format: formatter.NewNetworkFormat(format, options.quiet),
|
||||||
Trunc: !opts.noTrunc,
|
Trunc: !options.noTrunc,
|
||||||
}
|
}
|
||||||
return formatter.NetworkWrite(networksCtx, networkResources)
|
return formatter.NetworkWrite(networksCtx, networkResources)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,11 @@ package network
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type pruneOptions struct {
|
type pruneOptions struct {
|
||||||
|
@ -18,14 +17,14 @@ type pruneOptions struct {
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for networks
|
// NewPruneCommand returns a new cobra prune command for networks
|
||||||
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
options := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune [OPTIONS]",
|
Use: "prune [OPTIONS]",
|
||||||
Short: "Remove all unused networks",
|
Short: "Remove all unused networks",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
output, err := runPrune(dockerCli, opts)
|
output, err := runPrune(dockerCli, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -38,8 +37,8 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
|
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
|
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
|
||||||
|
|
||||||
return cmd
|
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.
|
const warning = `WARNING! This will remove all networks not used by at least one container.
|
||||||
Are you sure you want to continue?`
|
Are you sure you want to continue?`
|
||||||
|
|
||||||
func runPrune(dockerCli command.Cli, opts pruneOptions) (output string, err error) {
|
func runPrune(dockerCli command.Cli, options pruneOptions) (output string, err error) {
|
||||||
pruneFilters := command.PruneFilters(dockerCli, opts.filter.Value())
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type listOptions struct {
|
type listOptions struct {
|
||||||
|
@ -18,7 +17,7 @@ type listOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := listOptions{filter: opts.NewFilterOpt()}
|
options := listOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls [OPTIONS]",
|
Use: "ls [OPTIONS]",
|
||||||
|
@ -26,30 +25,30 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
Short: "List nodes in the swarm",
|
Short: "List nodes in the swarm",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runList(dockerCli, opts)
|
return runList(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print nodes using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print nodes using a Go template")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runList(dockerCli command.Cli, opts listOptions) error {
|
func runList(dockerCli command.Cli, options listOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
nodes, err := client.NodeList(
|
nodes, err := client.NodeList(
|
||||||
ctx,
|
ctx,
|
||||||
types.NodeListOptions{Filters: opts.filter.Value()})
|
types.NodeListOptions{Filters: options.filter.Value()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
info := types.Info{}
|
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
|
// only non-empty nodes and not quiet, should we call /info api
|
||||||
info, err = client.Info(ctx)
|
info, err = client.Info(ctx)
|
||||||
if err != nil {
|
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 {
|
if len(format) == 0 {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
if len(dockerCli.ConfigFile().NodesFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().NodesFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().NodesFormat
|
format = dockerCli.ConfigFile().NodesFormat
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nodesCtx := formatter.Context{
|
nodesCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewNodeFormat(format, opts.quiet),
|
Format: formatter.NewNodeFormat(format, options.quiet),
|
||||||
}
|
}
|
||||||
return formatter.NodeWrite(nodesCtx, nodes, info)
|
return formatter.NodeWrite(nodesCtx, nodes, info)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package node
|
package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/cli/opts"
|
||||||
)
|
)
|
||||||
|
|
||||||
type nodeOptions struct {
|
type nodeOptions struct {
|
||||||
|
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/cli/cli/command/idresolver"
|
"github.com/docker/cli/cli/command/idresolver"
|
||||||
"github.com/docker/cli/cli/command/task"
|
"github.com/docker/cli/cli/command/task"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -26,33 +26,33 @@ type psOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPsCommand(dockerCli command.Cli) *cobra.Command {
|
func newPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := psOptions{filter: opts.NewFilterOpt()}
|
options := psOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ps [OPTIONS] [NODE...]",
|
Use: "ps [OPTIONS] [NODE...]",
|
||||||
Short: "List tasks running on one or more nodes, defaults to current node",
|
Short: "List tasks running on one or more nodes, defaults to current node",
|
||||||
Args: cli.RequiresMinArgs(0),
|
Args: cli.RequiresMinArgs(0),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.nodeIDs = []string{"self"}
|
options.nodeIDs = []string{"self"}
|
||||||
|
|
||||||
if len(args) != 0 {
|
if len(args) != 0 {
|
||||||
opts.nodeIDs = args
|
options.nodeIDs = args
|
||||||
}
|
}
|
||||||
|
|
||||||
return runPs(dockerCli, opts)
|
return runPs(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output")
|
||||||
flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print tasks using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display task IDs")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPs(dockerCli command.Cli, opts psOptions) error {
|
func runPs(dockerCli command.Cli, options psOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ func runPs(dockerCli command.Cli, opts psOptions) error {
|
||||||
tasks []swarm.Task
|
tasks []swarm.Task
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, nodeID := range opts.nodeIDs {
|
for _, nodeID := range options.nodeIDs {
|
||||||
nodeRef, err := Reference(ctx, client, nodeID)
|
nodeRef, err := Reference(ctx, client, nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err.Error())
|
errs = append(errs, err.Error())
|
||||||
|
@ -74,7 +74,7 @@ func runPs(dockerCli command.Cli, opts psOptions) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := opts.filter.Value()
|
filter := options.filter.Value()
|
||||||
filter.Add("node", node.ID)
|
filter.Add("node", node.ID)
|
||||||
|
|
||||||
nodeTasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
|
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...)
|
tasks = append(tasks, nodeTasks...)
|
||||||
}
|
}
|
||||||
|
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
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
|
format = dockerCli.ConfigFile().TasksFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -96,7 +96,7 @@ func runPs(dockerCli command.Cli, opts psOptions) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) == 0 || len(tasks) != 0 {
|
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())
|
errs = append(errs, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -19,7 +19,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
nodeOpts := newNodeOptions()
|
options := newNodeOptions()
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "update [OPTIONS] NODE",
|
Use: "update [OPTIONS] NODE",
|
||||||
|
@ -31,9 +31,9 @@ func newUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVar(&nodeOpts.role, flagRole, "", `Role of the node ("worker"|"manager")`)
|
flags.StringVar(&options.role, flagRole, "", `Role of the node ("worker"|"manager")`)
|
||||||
flags.StringVar(&nodeOpts.availability, flagAvailability, "", `Availability of the node ("active"|"pause"|"drain")`)
|
flags.StringVar(&options.availability, flagAvailability, "", `Availability of the node ("active"|"pause"|"drain")`)
|
||||||
flags.Var(&nodeOpts.annotations.labels, flagLabelAdd, "Add or update a node label (key=value)")
|
flags.Var(&options.annotations.labels, flagLabelAdd, "Add or update a node label (key=value)")
|
||||||
labelKeys := opts.NewListOpts(nil)
|
labelKeys := opts.NewListOpts(nil)
|
||||||
flags.Var(&labelKeys, flagLabelRemove, "Remove a node label if exists")
|
flags.Var(&labelKeys, flagLabelRemove, "Remove a node label if exists")
|
||||||
return cmd
|
return cmd
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +17,7 @@ type listOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
opts := listOptions{filter: opts.NewFilterOpt()}
|
options := listOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls [OPTIONS]",
|
Use: "ls [OPTIONS]",
|
||||||
|
@ -25,29 +25,29 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
Aliases: []string{"list"},
|
Aliases: []string{"list"},
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runList(dockerCli, opts)
|
return runList(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display plugin IDs")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display plugin IDs")
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print plugins using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print plugins using a Go template")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Provide filter values (e.g. 'enabled=true')")
|
flags.VarP(&options.filter, "filter", "f", "Provide filter values (e.g. 'enabled=true')")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
func runList(dockerCli *command.DockerCli, options listOptions) error {
|
||||||
plugins, err := dockerCli.Client().PluginList(context.Background(), opts.filter.Value())
|
plugins, err := dockerCli.Client().PluginList(context.Background(), options.filter.Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().PluginsFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().PluginsFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().PluginsFormat
|
format = dockerCli.ConfigFile().PluginsFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -56,8 +56,8 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
||||||
|
|
||||||
pluginsCtx := formatter.Context{
|
pluginsCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewPluginFormat(format, opts.quiet),
|
Format: formatter.NewPluginFormat(format, options.quiet),
|
||||||
Trunc: !opts.noTrunc,
|
Trunc: !options.noTrunc,
|
||||||
}
|
}
|
||||||
return formatter.PluginWrite(pluginsCtx, plugins)
|
return formatter.PluginWrite(pluginsCtx, plugins)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/docker/cli/cli/command/image"
|
"github.com/docker/cli/cli/command/image"
|
||||||
"github.com/docker/cli/cli/command/network"
|
"github.com/docker/cli/cli/command/network"
|
||||||
"github.com/docker/cli/cli/command/volume"
|
"github.com/docker/cli/cli/command/volume"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,16 +6,15 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
registrytypes "github.com/docker/docker/api/types/registry"
|
registrytypes "github.com/docker/docker/api/types/registry"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/stringutils"
|
"github.com/docker/docker/pkg/stringutils"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type searchOptions struct {
|
type searchOptions struct {
|
||||||
|
@ -31,26 +30,26 @@ type searchOptions struct {
|
||||||
|
|
||||||
// NewSearchCommand creates a new `docker search` command
|
// NewSearchCommand creates a new `docker search` command
|
||||||
func NewSearchCommand(dockerCli command.Cli) *cobra.Command {
|
func NewSearchCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := searchOptions{filter: opts.NewFilterOpt()}
|
options := searchOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "search [OPTIONS] TERM",
|
Use: "search [OPTIONS] TERM",
|
||||||
Short: "Search the Docker Hub for images",
|
Short: "Search the Docker Hub for images",
|
||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.term = args[0]
|
options.term = args[0]
|
||||||
return runSearch(dockerCli, opts)
|
return runSearch(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
flags.IntVar(&opts.limit, "limit", registry.DefaultSearchLimit, "Max number of search results")
|
flags.IntVar(&options.limit, "limit", registry.DefaultSearchLimit, "Max number of search results")
|
||||||
|
|
||||||
flags.BoolVar(&opts.automated, "automated", false, "Only show automated builds")
|
flags.BoolVar(&options.automated, "automated", false, "Only show automated builds")
|
||||||
flags.UintVarP(&opts.stars, "stars", "s", 0, "Only displays with at least x stars")
|
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("automated", "use --filter=is-automated=true instead")
|
||||||
flags.MarkDeprecated("stars", "use --filter=stars=3 instead")
|
flags.MarkDeprecated("stars", "use --filter=stars=3 instead")
|
||||||
|
@ -58,8 +57,8 @@ func NewSearchCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSearch(dockerCli command.Cli, opts searchOptions) error {
|
func runSearch(dockerCli command.Cli, options searchOptions) error {
|
||||||
indexInfo, err := registry.ParseSearchIndexInfo(opts.term)
|
indexInfo, err := registry.ParseSearchIndexInfo(options.term)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -74,16 +73,16 @@ func runSearch(dockerCli command.Cli, opts searchOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
options := types.ImageSearchOptions{
|
searchOptions := types.ImageSearchOptions{
|
||||||
RegistryAuth: encodedAuth,
|
RegistryAuth: encodedAuth,
|
||||||
PrivilegeFunc: requestPrivilege,
|
PrivilegeFunc: requestPrivilege,
|
||||||
Filters: opts.filter.Value(),
|
Filters: options.filter.Value(),
|
||||||
Limit: opts.limit,
|
Limit: options.limit,
|
||||||
}
|
}
|
||||||
|
|
||||||
clnt := dockerCli.Client()
|
clnt := dockerCli.Client()
|
||||||
|
|
||||||
unorderedResults, err := clnt.ImageSearch(ctx, opts.term, options)
|
unorderedResults, err := clnt.ImageSearch(ctx, options.term, searchOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -95,12 +94,12 @@ func runSearch(dockerCli command.Cli, opts searchOptions) error {
|
||||||
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
|
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
|
||||||
for _, res := range results {
|
for _, res := range results {
|
||||||
// --automated and -s, --stars are deprecated since Docker 1.12
|
// --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
|
continue
|
||||||
}
|
}
|
||||||
desc := strings.Replace(res.Description, "\n", " ", -1)
|
desc := strings.Replace(res.Description, "\n", " ", -1)
|
||||||
desc = strings.Replace(desc, "\r", " ", -1)
|
desc = strings.Replace(desc, "\r", " ", -1)
|
||||||
if !opts.noTrunc {
|
if !options.noTrunc {
|
||||||
desc = stringutils.Ellipsis(desc, 45)
|
desc = stringutils.Ellipsis(desc, 45)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount)
|
fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount)
|
||||||
|
|
|
@ -7,8 +7,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/system"
|
"github.com/docker/docker/pkg/system"
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -23,7 +23,7 @@ type createOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command {
|
func newSecretCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
createOpts := createOptions{
|
options := createOptions{
|
||||||
labels: opts.NewListOpts(opts.ValidateEnv),
|
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",
|
Short: "Create a secret from a file or STDIN as content",
|
||||||
Args: cli.ExactArgs(2),
|
Args: cli.ExactArgs(2),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
createOpts.name = args[0]
|
options.name = args[0]
|
||||||
createOpts.file = args[1]
|
options.file = args[1]
|
||||||
return runSecretCreate(dockerCli, createOpts)
|
return runSecretCreate(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.VarP(&createOpts.labels, "label", "l", "Secret labels")
|
flags.VarP(&options.labels, "label", "l", "Secret labels")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +17,7 @@ type listOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSecretListCommand(dockerCli command.Cli) *cobra.Command {
|
func newSecretListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := listOptions{filter: opts.NewFilterOpt()}
|
options := listOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls [OPTIONS]",
|
Use: "ls [OPTIONS]",
|
||||||
|
@ -25,29 +25,29 @@ func newSecretListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
Short: "List secrets",
|
Short: "List secrets",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runSecretList(dockerCli, opts)
|
return runSecretList(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
|
||||||
flags.StringVarP(&opts.format, "format", "", "", "Pretty-print secrets using a Go template")
|
flags.StringVarP(&options.format, "format", "", "", "Pretty-print secrets using a Go template")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSecretList(dockerCli command.Cli, opts listOptions) error {
|
func runSecretList(dockerCli command.Cli, options listOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().SecretFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().SecretFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().SecretFormat
|
format = dockerCli.ConfigFile().SecretFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -55,7 +55,7 @@ func runSecretList(dockerCli command.Cli, opts listOptions) error {
|
||||||
}
|
}
|
||||||
secretCtx := formatter.Context{
|
secretCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewSecretFormat(format, opts.quiet),
|
Format: formatter.NewSecretFormat(format, options.quiet),
|
||||||
}
|
}
|
||||||
return formatter.SecretWrite(secretCtx, secrets)
|
return formatter.SecretWrite(secretCtx, secrets)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@ import (
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -21,7 +21,7 @@ type listOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
opts := listOptions{filter: opts.NewFilterOpt()}
|
options := listOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls [OPTIONS]",
|
Use: "ls [OPTIONS]",
|
||||||
|
@ -29,30 +29,30 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
Short: "List services",
|
Short: "List services",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runList(dockerCli, opts)
|
return runList(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print services using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print services using a Go template")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
func runList(dockerCli *command.DockerCli, options listOptions) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
|
|
||||||
serviceFilters := opts.filter.Value()
|
serviceFilters := options.filter.Value()
|
||||||
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceFilters})
|
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: serviceFilters})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
info := map[string]formatter.ServiceListInfo{}
|
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
|
// only non-empty services and not quiet, should we call TaskList and NodeList api
|
||||||
taskFilter := filters.NewArgs()
|
taskFilter := filters.NewArgs()
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
|
@ -72,9 +72,9 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
||||||
info = GetServicesStatus(services, nodes, tasks)
|
info = GetServicesStatus(services, nodes, tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().ServicesFormat
|
format = dockerCli.ConfigFile().ServicesFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -83,7 +83,7 @@ func runList(dockerCli *command.DockerCli, opts listOptions) error {
|
||||||
|
|
||||||
servicesCtx := formatter.Context{
|
servicesCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewServiceListFormat(format, opts.quiet),
|
Format: formatter.NewServiceListFormat(format, options.quiet),
|
||||||
}
|
}
|
||||||
return formatter.ServiceListWrite(servicesCtx, services, info)
|
return formatter.ServiceListWrite(servicesCtx, services, info)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,10 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
"github.com/docker/swarmkit/api"
|
"github.com/docker/swarmkit/api"
|
||||||
"github.com/docker/swarmkit/api/defaults"
|
"github.com/docker/swarmkit/api/defaults"
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,19 +3,18 @@ package service
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/cli/cli/command/idresolver"
|
"github.com/docker/cli/cli/command/idresolver"
|
||||||
"github.com/docker/cli/cli/command/node"
|
"github.com/docker/cli/cli/command/node"
|
||||||
"github.com/docker/cli/cli/command/task"
|
"github.com/docker/cli/cli/command/task"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type psOptions struct {
|
type psOptions struct {
|
||||||
|
@ -28,36 +27,36 @@ type psOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPsCommand(dockerCli command.Cli) *cobra.Command {
|
func newPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := psOptions{filter: opts.NewFilterOpt()}
|
options := psOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ps [OPTIONS] SERVICE [SERVICE...]",
|
Use: "ps [OPTIONS] SERVICE [SERVICE...]",
|
||||||
Short: "List the tasks of one or more services",
|
Short: "List the tasks of one or more services",
|
||||||
Args: cli.RequiresMinArgs(1),
|
Args: cli.RequiresMinArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.services = args
|
options.services = args
|
||||||
return runPS(dockerCli, opts)
|
return runPS(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display task IDs")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output")
|
||||||
flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print tasks using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPS(dockerCli command.Cli, opts psOptions) error {
|
func runPS(dockerCli command.Cli, options psOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
filter := opts.filter.Value()
|
filter := options.filter.Value()
|
||||||
|
|
||||||
serviceIDFilter := filters.NewArgs()
|
serviceIDFilter := filters.NewArgs()
|
||||||
serviceNameFilter := filters.NewArgs()
|
serviceNameFilter := filters.NewArgs()
|
||||||
for _, service := range opts.services {
|
for _, service := range options.services {
|
||||||
serviceIDFilter.Add("id", service)
|
serviceIDFilter.Add("id", service)
|
||||||
serviceNameFilter.Add("name", service)
|
serviceNameFilter.Add("name", service)
|
||||||
}
|
}
|
||||||
|
@ -70,7 +69,7 @@ func runPS(dockerCli command.Cli, opts psOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, service := range opts.services {
|
for _, service := range options.services {
|
||||||
serviceCount := 0
|
serviceCount := 0
|
||||||
// Lookup by ID/Prefix
|
// Lookup by ID/Prefix
|
||||||
for _, serviceEntry := range serviceByIDList {
|
for _, serviceEntry := range serviceByIDList {
|
||||||
|
@ -110,14 +109,14 @@ func runPS(dockerCli command.Cli, opts psOptions) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().TasksFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().TasksFormat
|
format = dockerCli.ConfigFile().TasksFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,13 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
mounttypes "github.com/docker/docker/api/types/mount"
|
mounttypes "github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
"github.com/docker/swarmkit/api/defaults"
|
"github.com/docker/swarmkit/api/defaults"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -24,14 +24,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
serviceOpts := newServiceOptions()
|
options := newServiceOptions()
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "update [OPTIONS] SERVICE",
|
Use: "update [OPTIONS] SERVICE",
|
||||||
Short: "Update a service",
|
Short: "Update a service",
|
||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
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.SetAnnotation("rollback", "version", []string{"1.25"})
|
||||||
flags.Bool("force", false, "Force update even if no changes require it")
|
flags.Bool("force", false, "Force update even if no changes require it")
|
||||||
flags.SetAnnotation("force", "version", []string{"1.25"})
|
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(), flagEnvRemove, "Remove an environment variable")
|
||||||
flags.Var(newListOptsVar(), flagGroupRemove, "Remove a previously added supplementary user group from the container")
|
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.SetAnnotation(flagDNSSearchRemove, "version", []string{"1.25"})
|
||||||
flags.Var(newListOptsVar(), flagHostRemove, "Remove a custom host-to-IP mapping (host:ip)")
|
flags.Var(newListOptsVar(), flagHostRemove, "Remove a custom host-to-IP mapping (host:ip)")
|
||||||
flags.SetAnnotation(flagHostRemove, "version", []string{"1.25"})
|
flags.SetAnnotation(flagHostRemove, "version", []string{"1.25"})
|
||||||
flags.Var(&serviceOpts.labels, flagLabelAdd, "Add or update a service label")
|
flags.Var(&options.labels, flagLabelAdd, "Add or update a service label")
|
||||||
flags.Var(&serviceOpts.containerLabels, flagContainerLabelAdd, "Add or update a container label")
|
flags.Var(&options.containerLabels, flagContainerLabelAdd, "Add or update a container label")
|
||||||
flags.Var(&serviceOpts.env, flagEnvAdd, "Add or update an environment variable")
|
flags.Var(&options.env, flagEnvAdd, "Add or update an environment variable")
|
||||||
flags.Var(newListOptsVar(), flagSecretRemove, "Remove a secret")
|
flags.Var(newListOptsVar(), flagSecretRemove, "Remove a secret")
|
||||||
flags.SetAnnotation(flagSecretRemove, "version", []string{"1.25"})
|
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.SetAnnotation(flagSecretAdd, "version", []string{"1.25"})
|
||||||
|
|
||||||
flags.Var(newListOptsVar(), flagConfigRemove, "Remove a configuration file")
|
flags.Var(newListOptsVar(), flagConfigRemove, "Remove a configuration file")
|
||||||
flags.SetAnnotation(flagConfigRemove, "version", []string{"1.30"})
|
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.SetAnnotation(flagConfigAdd, "version", []string{"1.30"})
|
||||||
|
|
||||||
flags.Var(&serviceOpts.mounts, flagMountAdd, "Add or update a mount on a service")
|
flags.Var(&options.mounts, flagMountAdd, "Add or update a mount on a service")
|
||||||
flags.Var(&serviceOpts.constraints, flagConstraintAdd, "Add or update a placement constraint")
|
flags.Var(&options.constraints, flagConstraintAdd, "Add or update a placement constraint")
|
||||||
flags.Var(&serviceOpts.placementPrefs, flagPlacementPrefAdd, "Add a placement preference")
|
flags.Var(&options.placementPrefs, flagPlacementPrefAdd, "Add a placement preference")
|
||||||
flags.SetAnnotation(flagPlacementPrefAdd, "version", []string{"1.28"})
|
flags.SetAnnotation(flagPlacementPrefAdd, "version", []string{"1.28"})
|
||||||
flags.Var(&placementPrefOpts{}, flagPlacementPrefRemove, "Remove a placement preference")
|
flags.Var(&placementPrefOpts{}, flagPlacementPrefRemove, "Remove a placement preference")
|
||||||
flags.SetAnnotation(flagPlacementPrefRemove, "version", []string{"1.28"})
|
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.SetAnnotation(flagNetworkAdd, "version", []string{"1.29"})
|
||||||
flags.Var(newListOptsVar(), flagNetworkRemove, "Remove a network")
|
flags.Var(newListOptsVar(), flagNetworkRemove, "Remove a network")
|
||||||
flags.SetAnnotation(flagNetworkRemove, "version", []string{"1.29"})
|
flags.SetAnnotation(flagNetworkRemove, "version", []string{"1.29"})
|
||||||
flags.Var(&serviceOpts.endpoint.publishPorts, flagPublishAdd, "Add or update a published port")
|
flags.Var(&options.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.groups, flagGroupAdd, "Add an additional supplementary user group to the container")
|
||||||
flags.SetAnnotation(flagGroupAdd, "version", []string{"1.25"})
|
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.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.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.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"})
|
flags.SetAnnotation(flagHostAdd, "version", []string{"1.25"})
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
@ -104,7 +104,7 @@ func newListOptsVar() *opts.ListOpts {
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: gocyclo
|
// 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()
|
apiClient := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
|
@ -214,7 +214,7 @@ func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *service
|
||||||
|
|
||||||
fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
|
fmt.Fprintf(dockerCli.Out(), "%s\n", serviceID)
|
||||||
|
|
||||||
if opts.detach {
|
if options.detach {
|
||||||
if !flags.Changed("detach") {
|
if !flags.Changed("detach") {
|
||||||
fmt.Fprintln(dockerCli.Err(), "Since --detach=false was not specified, tasks will be updated in the background.\n"+
|
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.")
|
"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 nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return waitOnService(ctx, dockerCli, serviceID, opts)
|
return waitOnService(ctx, dockerCli, serviceID, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint: gocyclo
|
// nolint: gocyclo
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
package stack
|
package stack
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli/compose/convert"
|
"github.com/docker/cli/cli/compose/convert"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/opts"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getStackFilter(namespace string) filters.Args {
|
func getStackFilter(namespace string) filters.Args {
|
||||||
|
|
|
@ -3,16 +3,15 @@ package stack
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/cli/cli/command/idresolver"
|
"github.com/docker/cli/cli/command/idresolver"
|
||||||
"github.com/docker/cli/cli/command/task"
|
"github.com/docker/cli/cli/command/task"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type psOptions struct {
|
type psOptions struct {
|
||||||
|
@ -25,33 +24,33 @@ type psOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPsCommand(dockerCli command.Cli) *cobra.Command {
|
func newPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := psOptions{filter: opts.NewFilterOpt()}
|
options := psOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ps [OPTIONS] STACK",
|
Use: "ps [OPTIONS] STACK",
|
||||||
Short: "List the tasks in the stack",
|
Short: "List the tasks in the stack",
|
||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.namespace = args[0]
|
options.namespace = args[0]
|
||||||
return runPS(dockerCli, opts)
|
return runPS(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
|
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output")
|
||||||
flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display task IDs")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print tasks using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPS(dockerCli command.Cli, opts psOptions) error {
|
func runPS(dockerCli command.Cli, options psOptions) error {
|
||||||
namespace := opts.namespace
|
namespace := options.namespace
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
filter := getStackFilterFromOpt(opts.namespace, opts.filter)
|
filter := getStackFilterFromOpt(options.namespace, options.filter)
|
||||||
|
|
||||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
|
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -63,14 +62,14 @@ func runPS(dockerCli command.Cli, opts psOptions) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().TasksFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().TasksFormat
|
format = dockerCli.ConfigFile().TasksFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,15 @@ package stack
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/docker/cli/cli/command/service"
|
"github.com/docker/cli/cli/command/service"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type servicesOptions struct {
|
type servicesOptions struct {
|
||||||
|
@ -23,30 +22,30 @@ type servicesOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newServicesCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func newServicesCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
opts := servicesOptions{filter: opts.NewFilterOpt()}
|
options := servicesOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "services [OPTIONS] STACK",
|
Use: "services [OPTIONS] STACK",
|
||||||
Short: "List the services in the stack",
|
Short: "List the services in the stack",
|
||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
opts.namespace = args[0]
|
options.namespace = args[0]
|
||||||
return runServices(dockerCli, opts)
|
return runServices(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display IDs")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print services using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print services using a Go template")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runServices(dockerCli *command.DockerCli, opts servicesOptions) error {
|
func runServices(dockerCli *command.DockerCli, options servicesOptions) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
|
|
||||||
filter := getStackFilterFromOpt(opts.namespace, opts.filter)
|
filter := getStackFilterFromOpt(options.namespace, options.filter)
|
||||||
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: filter})
|
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: filter})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 no services in this stack, print message and exit 0
|
||||||
if len(services) == 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
info := map[string]formatter.ServiceListInfo{}
|
info := map[string]formatter.ServiceListInfo{}
|
||||||
if !opts.quiet {
|
if !options.quiet {
|
||||||
taskFilter := filters.NewArgs()
|
taskFilter := filters.NewArgs()
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
taskFilter.Add("service", service.ID)
|
taskFilter.Add("service", service.ID)
|
||||||
|
@ -80,9 +79,9 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error {
|
||||||
info = service.GetServicesStatus(services, nodes, tasks)
|
info = service.GetServicesStatus(services, nodes, tasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().ServicesFormat
|
format = dockerCli.ConfigFile().ServicesFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -91,7 +90,7 @@ func runServices(dockerCli *command.DockerCli, opts servicesOptions) error {
|
||||||
|
|
||||||
servicesCtx := formatter.Context{
|
servicesCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewServiceListFormat(format, opts.quiet),
|
Format: formatter.NewServiceListFormat(format, options.quiet),
|
||||||
}
|
}
|
||||||
return formatter.ServiceListWrite(servicesCtx, services, info)
|
return formatter.ServiceListWrite(servicesCtx, services, info)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
|
@ -9,16 +9,15 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
eventtypes "github.com/docker/docker/api/types/events"
|
eventtypes "github.com/docker/docker/api/types/events"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/docker/docker/pkg/jsonlog"
|
"github.com/docker/docker/pkg/jsonlog"
|
||||||
"github.com/docker/docker/pkg/templates"
|
"github.com/docker/docker/pkg/templates"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type eventsOptions struct {
|
type eventsOptions struct {
|
||||||
|
@ -30,41 +29,41 @@ type eventsOptions struct {
|
||||||
|
|
||||||
// NewEventsCommand creates a new cobra.Command for `docker events`
|
// NewEventsCommand creates a new cobra.Command for `docker events`
|
||||||
func NewEventsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
func NewEventsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||||
opts := eventsOptions{filter: opts.NewFilterOpt()}
|
options := eventsOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "events [OPTIONS]",
|
Use: "events [OPTIONS]",
|
||||||
Short: "Get real time events from the server",
|
Short: "Get real time events from the server",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runEvents(dockerCli, &opts)
|
return runEvents(dockerCli, &options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.StringVar(&opts.since, "since", "", "Show all events created since timestamp")
|
flags.StringVar(&options.since, "since", "", "Show all events created since timestamp")
|
||||||
flags.StringVar(&opts.until, "until", "", "Stream events until this timestamp")
|
flags.StringVar(&options.until, "until", "", "Stream events until this timestamp")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
|
flags.VarP(&options.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.format, "format", "", "Format the output using the given Go template")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runEvents(dockerCli *command.DockerCli, opts *eventsOptions) error {
|
func runEvents(dockerCli *command.DockerCli, options *eventsOptions) error {
|
||||||
tmpl, err := makeTemplate(opts.format)
|
tmpl, err := makeTemplate(options.format)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.StatusError{
|
return cli.StatusError{
|
||||||
StatusCode: 64,
|
StatusCode: 64,
|
||||||
Status: "Error parsing format: " + err.Error()}
|
Status: "Error parsing format: " + err.Error()}
|
||||||
}
|
}
|
||||||
options := types.EventsOptions{
|
eventOptions := types.EventsOptions{
|
||||||
Since: opts.since,
|
Since: options.since,
|
||||||
Until: opts.until,
|
Until: options.until,
|
||||||
Filters: opts.filter.Value(),
|
Filters: options.filter.Value(),
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
events, errs := dockerCli.Client().Events(ctx, options)
|
events, errs := dockerCli.Client().Events(ctx, eventOptions)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
out := dockerCli.Out()
|
out := dockerCli.Out()
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/prune"
|
"github.com/docker/cli/cli/command/prune"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/cli/opts"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
@ -19,22 +19,22 @@ type pruneOptions struct {
|
||||||
|
|
||||||
// NewPruneCommand creates a new cobra.Command for `docker prune`
|
// NewPruneCommand creates a new cobra.Command for `docker prune`
|
||||||
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
options := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune [OPTIONS]",
|
Use: "prune [OPTIONS]",
|
||||||
Short: "Remove unused data",
|
Short: "Remove unused data",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runPrune(dockerCli, opts)
|
return runPrune(dockerCli, options)
|
||||||
},
|
},
|
||||||
Tags: map[string]string{"version": "1.25"},
|
Tags: map[string]string{"version": "1.25"},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
|
flags.BoolVarP(&options.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.BoolVarP(&options.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.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=<timestamp>')")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
volumetypes "github.com/docker/docker/api/types/volume"
|
volumetypes "github.com/docker/docker/api/types/volume"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -21,7 +21,7 @@ type createOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := createOptions{
|
options := createOptions{
|
||||||
driverOpts: *opts.NewMapOpts(nil, nil),
|
driverOpts: *opts.NewMapOpts(nil, nil),
|
||||||
labels: opts.NewListOpts(opts.ValidateEnv),
|
labels: opts.NewListOpts(opts.ValidateEnv),
|
||||||
}
|
}
|
||||||
|
@ -32,32 +32,32 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
Args: cli.RequiresMaxArgs(1),
|
Args: cli.RequiresMaxArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) == 1 {
|
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")
|
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 := cmd.Flags()
|
||||||
flags.StringVarP(&opts.driver, "driver", "d", "local", "Specify volume driver name")
|
flags.StringVarP(&options.driver, "driver", "d", "local", "Specify volume driver name")
|
||||||
flags.StringVar(&opts.name, "name", "", "Specify volume name")
|
flags.StringVar(&options.name, "name", "", "Specify volume name")
|
||||||
flags.Lookup("name").Hidden = true
|
flags.Lookup("name").Hidden = true
|
||||||
flags.VarP(&opts.driverOpts, "opt", "o", "Set driver specific options")
|
flags.VarP(&options.driverOpts, "opt", "o", "Set driver specific options")
|
||||||
flags.Var(&opts.labels, "label", "Set metadata for a volume")
|
flags.Var(&options.labels, "label", "Set metadata for a volume")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCreate(dockerCli command.Cli, opts createOptions) error {
|
func runCreate(dockerCli command.Cli, options createOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
|
|
||||||
volReq := volumetypes.VolumesCreateBody{
|
volReq := volumetypes.VolumesCreateBody{
|
||||||
Driver: opts.driver,
|
Driver: options.driver,
|
||||||
DriverOpts: opts.driverOpts.GetAll(),
|
DriverOpts: options.driverOpts.GetAll(),
|
||||||
Name: opts.name,
|
Name: options.name,
|
||||||
Labels: runconfigopts.ConvertKVStringsToMap(opts.labels.GetAll()),
|
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
|
||||||
}
|
}
|
||||||
|
|
||||||
vol, err := client.VolumeCreate(context.Background(), volReq)
|
vol, err := client.VolumeCreate(context.Background(), volReq)
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/command/formatter"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -27,7 +27,7 @@ type listOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := listOptions{filter: opts.NewFilterOpt()}
|
options := listOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "ls [OPTIONS]",
|
Use: "ls [OPTIONS]",
|
||||||
|
@ -35,28 +35,28 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
Short: "List volumes",
|
Short: "List volumes",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runList(dockerCli, opts)
|
return runList(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only display volume names")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display volume names")
|
||||||
flags.StringVar(&opts.format, "format", "", "Pretty-print volumes using a Go template")
|
flags.StringVar(&options.format, "format", "", "Pretty-print volumes using a Go template")
|
||||||
flags.VarP(&opts.filter, "filter", "f", "Provide filter values (e.g. 'dangling=true')")
|
flags.VarP(&options.filter, "filter", "f", "Provide filter values (e.g. 'dangling=true')")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func runList(dockerCli command.Cli, opts listOptions) error {
|
func runList(dockerCli command.Cli, options listOptions) error {
|
||||||
client := dockerCli.Client()
|
client := dockerCli.Client()
|
||||||
volumes, err := client.VolumeList(context.Background(), opts.filter.Value())
|
volumes, err := client.VolumeList(context.Background(), options.filter.Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
format := opts.format
|
format := options.format
|
||||||
if len(format) == 0 {
|
if len(format) == 0 {
|
||||||
if len(dockerCli.ConfigFile().VolumesFormat) > 0 && !opts.quiet {
|
if len(dockerCli.ConfigFile().VolumesFormat) > 0 && !options.quiet {
|
||||||
format = dockerCli.ConfigFile().VolumesFormat
|
format = dockerCli.ConfigFile().VolumesFormat
|
||||||
} else {
|
} else {
|
||||||
format = formatter.TableFormatKey
|
format = formatter.TableFormatKey
|
||||||
|
@ -67,7 +67,7 @@ func runList(dockerCli command.Cli, opts listOptions) error {
|
||||||
|
|
||||||
volumeCtx := formatter.Context{
|
volumeCtx := formatter.Context{
|
||||||
Output: dockerCli.Out(),
|
Output: dockerCli.Out(),
|
||||||
Format: formatter.NewVolumeFormat(format, opts.quiet),
|
Format: formatter.NewVolumeFormat(format, options.quiet),
|
||||||
}
|
}
|
||||||
return formatter.VolumeWrite(volumeCtx, volumes.Volumes)
|
return formatter.VolumeWrite(volumeCtx, volumes.Volumes)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/cli/opts"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -18,14 +18,14 @@ type pruneOptions struct {
|
||||||
|
|
||||||
// NewPruneCommand returns a new cobra prune command for volumes
|
// NewPruneCommand returns a new cobra prune command for volumes
|
||||||
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
opts := pruneOptions{filter: opts.NewFilterOpt()}
|
options := pruneOptions{filter: opts.NewFilterOpt()}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune [OPTIONS]",
|
Use: "prune [OPTIONS]",
|
||||||
Short: "Remove all unused volumes",
|
Short: "Remove all unused volumes",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
spaceReclaimed, output, err := runPrune(dockerCli, opts)
|
spaceReclaimed, output, err := runPrune(dockerCli, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,8 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&opts.force, "force", "f", false, "Do not prompt for confirmation")
|
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
flags.Var(&opts.filter, "filter", "Provide filter values (e.g. 'label=<label>')")
|
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'label=<label>')")
|
||||||
|
|
||||||
return cmd
|
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.
|
const warning = `WARNING! This will remove all volumes not used by at least one container.
|
||||||
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) {
|
func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint64, output string, err error) {
|
||||||
pruneFilters := command.PruneFilters(dockerCli, opts.filter.Value())
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,11 @@ import (
|
||||||
|
|
||||||
servicecli "github.com/docker/cli/cli/command/service"
|
servicecli "github.com/docker/cli/cli/command/service"
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
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/container"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/docker/cli/cli/compose/schema"
|
"github.com/docker/cli/cli/compose/schema"
|
||||||
"github.com/docker/cli/cli/compose/template"
|
"github.com/docker/cli/cli/compose/template"
|
||||||
"github.com/docker/cli/cli/compose/types"
|
"github.com/docker/cli/cli/compose/types"
|
||||||
"github.com/docker/docker/opts"
|
"github.com/docker/cli/opts"
|
||||||
runconfigopts "github.com/docker/docker/runconfig/opts"
|
runconfigopts "github.com/docker/docker/runconfig/opts"
|
||||||
"github.com/docker/go-connections/nat"
|
"github.com/docker/go-connections/nat"
|
||||||
units "github.com/docker/go-units"
|
units "github.com/docker/go-units"
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
cliconfig "github.com/docker/cli/cli/config"
|
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/docker/go-connections/tlsconfig"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
@ -0,0 +1,6 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package opts
|
||||||
|
|
||||||
|
// DefaultHost constant defines the default host string used by docker on Windows
|
||||||
|
var DefaultHost = "npipe://" + DefaultNamedPipe
|
|
@ -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"
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
|
@ -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"
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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}
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
Loading…
Reference in New Issue