diff --git a/components/cli/.github/CODEOWNERS b/components/cli/.github/CODEOWNERS index 855eff98ab..f3164a64b5 100644 --- a/components/cli/.github/CODEOWNERS +++ b/components/cli/.github/CODEOWNERS @@ -1,8 +1,8 @@ # Github code owners # See https://github.com/blog/2392-introducing-code-owners -cli/compose/* @dnephin @vdemeester -contrib/completion/bash/* @albers -contrib/completion/zsh/* @sdurrheimer -docs/* @mstanleyjones @vdemeester @thaJeztah -scripts/* @dnephin +cli/compose/** @dnephin @vdemeester +contrib/completion/bash/** @albers +contrib/completion/zsh/** @sdurrheimer +docs/** @mstanleyjones @vdemeester @thaJeztah +scripts/** @dnephin diff --git a/components/cli/Makefile b/components/cli/Makefile index dbb3d3bfef..926b39b25b 100644 --- a/components/cli/Makefile +++ b/components/cli/Makefile @@ -54,6 +54,11 @@ manpages: yamldocs: scripts/docs/generate-yaml.sh +## Shellcheck validation +.PHONY: shellcheck +shellcheck: + scripts/validate/shellcheck + cli/compose/schema/bindata.go: cli/compose/schema/data/*.json go generate github.com/docker/cli/cli/compose/schema diff --git a/components/cli/circle.yml b/components/cli/circle.yml index 4552ee6dd9..2a9c196750 100644 --- a/components/cli/circle.yml +++ b/components/cli/circle.yml @@ -4,7 +4,7 @@ jobs: lint: working_directory: /work - docker: [{image: 'docker:17.05-git'}] + docker: [{image: 'docker:17.06-git'}] steps: - checkout - setup_remote_docker: @@ -22,7 +22,7 @@ jobs: cross: working_directory: /work - docker: [{image: 'docker:17.05-git'}] + docker: [{image: 'docker:17.06-git'}] parallelism: 3 steps: - checkout @@ -48,7 +48,7 @@ jobs: test: working_directory: /work - docker: [{image: 'docker:17.05-git'}] + docker: [{image: 'docker:17.06-git'}] steps: - checkout - setup_remote_docker: @@ -75,9 +75,8 @@ jobs: validate: working_directory: /work - docker: [{image: 'docker:17.05-git'}] + docker: [{image: 'docker:17.06-git'}] steps: - - run: apk add -U git openssh - checkout - setup_remote_docker: reusable: true @@ -91,7 +90,20 @@ jobs: docker build -f $dockerfile --tag cli-builder-with-git:$CIRCLE_BUILD_NUM . docker run --rm cli-builder-with-git:$CIRCLE_BUILD_NUM \ make -B vendor compose-jsonschema manpages yamldocs - + shellcheck: + working_directory: /work + docker: [{image: 'docker:17.06-git'}] + steps: + - checkout + - setup_remote_docker + - run: + name: "Run shellcheck" + command: | + dockerfile=dockerfiles/Dockerfile.shellcheck + echo "COPY . ." >> $dockerfile + docker build -f $dockerfile --tag cli-validator:$CIRCLE_BUILD_NUM . + docker run --rm cli-validator:$CIRCLE_BUILD_NUM \ + make -B shellcheck workflows: version: 2 ci: @@ -100,3 +112,4 @@ workflows: - cross - test - validate + - shellcheck diff --git a/components/cli/cli/command/node/list.go b/components/cli/cli/command/node/list.go index 0c3d7e1abc..7dac795663 100644 --- a/components/cli/cli/command/node/list.go +++ b/components/cli/cli/command/node/list.go @@ -1,15 +1,27 @@ package node import ( + "sort" + "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/opts" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/swarm" "github.com/spf13/cobra" "golang.org/x/net/context" + "vbom.ml/util/sortorder" ) +type byHostname []swarm.Node + +func (n byHostname) Len() int { return len(n) } +func (n byHostname) Swap(i, j int) { n[i], n[j] = n[j], n[i] } +func (n byHostname) Less(i, j int) bool { + return sortorder.NaturalLess(n[i].Description.Hostname, n[j].Description.Hostname) +} + type listOptions struct { quiet bool format string @@ -68,5 +80,6 @@ func runList(dockerCli command.Cli, options listOptions) error { Output: dockerCli.Out(), Format: formatter.NewNodeFormat(format, options.quiet), } + sort.Sort(byHostname(nodes)) return formatter.NodeWrite(nodesCtx, nodes, info) } diff --git a/components/cli/cli/command/node/list_test.go b/components/cli/cli/command/node/list_test.go index eb58d7709e..f579ebc88a 100644 --- a/components/cli/cli/command/node/list_test.go +++ b/components/cli/cli/command/node/list_test.go @@ -9,6 +9,8 @@ import ( "github.com/docker/cli/cli/internal/test" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/swarm" + "github.com/docker/docker/pkg/testutil" + "github.com/docker/docker/pkg/testutil/golden" "github.com/pkg/errors" // Import builders to get the builder function as package function . "github.com/docker/cli/cli/internal/test/builders" @@ -156,3 +158,22 @@ func TestNodeListFormat(t *testing.T) { assert.Contains(t, buf.String(), `nodeHostname1: Leader`) assert.Contains(t, buf.String(), `nodeHostname2: Reachable`) } + +func TestNodeListOrder(t *testing.T) { + cli := test.NewFakeCli(&fakeClient{ + nodeListFunc: func() ([]swarm.Node, error) { + return []swarm.Node{ + *Node(Hostname("node-2-foo"), Manager(Leader())), + *Node(Hostname("node-10-foo"), Manager()), + *Node(Hostname("node-1-foo")), + }, nil + + }, + }) + cmd := newListCommand(cli) + cmd.Flags().Set("format", "{{.Hostname}}: {{.ManagerStatus}}") + assert.NoError(t, cmd.Execute()) + actual := cli.OutBuffer().String() + expected := golden.Get(t, []byte(actual), "node-list-sort.golden") + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) +} diff --git a/components/cli/cli/command/node/testdata/node-list-sort.golden b/components/cli/cli/command/node/testdata/node-list-sort.golden new file mode 100644 index 0000000000..e2f2811994 --- /dev/null +++ b/components/cli/cli/command/node/testdata/node-list-sort.golden @@ -0,0 +1,3 @@ +node-1-foo: +node-2-foo: Leader +node-10-foo: Reachable diff --git a/components/cli/cli/command/prune/prune.go b/components/cli/cli/command/prune/prune.go deleted file mode 100644 index 12429da907..0000000000 --- a/components/cli/cli/command/prune/prune.go +++ /dev/null @@ -1,30 +0,0 @@ -package prune - -import ( - "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/container" - "github.com/docker/cli/cli/command/image" - "github.com/docker/cli/cli/command/network" - "github.com/docker/cli/cli/command/volume" - "github.com/docker/cli/opts" -) - -// RunContainerPrune executes a prune command for containers -func RunContainerPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) { - return container.RunPrune(dockerCli, filter) -} - -// RunVolumePrune executes a prune command for volumes -func RunVolumePrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) { - return volume.RunPrune(dockerCli, filter) -} - -// RunImagePrune executes a prune command for images -func RunImagePrune(dockerCli command.Cli, all bool, filter opts.FilterOpt) (uint64, string, error) { - return image.RunPrune(dockerCli, all, filter) -} - -// RunNetworkPrune executes a prune command for networks -func RunNetworkPrune(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) { - return network.RunPrune(dockerCli, filter) -} diff --git a/components/cli/cli/command/stack/list.go b/components/cli/cli/command/stack/list.go index f3781d260c..24da30512f 100644 --- a/components/cli/cli/command/stack/list.go +++ b/components/cli/cli/command/stack/list.go @@ -12,6 +12,7 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" + "vbom.ml/util/sortorder" ) type listOptions struct { @@ -60,7 +61,7 @@ type byName []*formatter.Stack func (n byName) Len() int { return len(n) } func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] } -func (n byName) Less(i, j int) bool { return n[i].Name < n[j].Name } +func (n byName) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Name, n[j].Name) } func getStacks(ctx context.Context, apiclient client.APIClient) ([]*formatter.Stack, error) { services, err := apiclient.ServiceList( diff --git a/components/cli/cli/command/stack/list_test.go b/components/cli/cli/command/stack/list_test.go index de1aa02365..48b141653f 100644 --- a/components/cli/cli/command/stack/list_test.go +++ b/components/cli/cli/command/stack/list_test.go @@ -98,10 +98,13 @@ func TestListWithoutFormat(t *testing.T) { } func TestListOrder(t *testing.T) { - buf := new(bytes.Buffer) - cmd := newListCommand(test.NewFakeCliWithOutput(&fakeClient{ - serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { - return []swarm.Service{ + usecases := []struct { + golden string + swarmServices []swarm.Service + }{ + { + golden: "stack-list-sort.golden", + swarmServices: []swarm.Service{ *Service( ServiceLabels(map[string]string{ "com.docker.stack.namespace": "service-name-foo", @@ -112,11 +115,40 @@ func TestListOrder(t *testing.T) { "com.docker.stack.namespace": "service-name-bar", }), ), - }, nil + }, }, - }, buf)) - assert.NoError(t, cmd.Execute()) - actual := buf.String() - expected := golden.Get(t, []byte(actual), "stack-list-sort.golden") - testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + { + golden: "stack-list-sort-natural.golden", + swarmServices: []swarm.Service{ + *Service( + ServiceLabels(map[string]string{ + "com.docker.stack.namespace": "service-name-1-foo", + }), + ), + *Service( + ServiceLabels(map[string]string{ + "com.docker.stack.namespace": "service-name-10-foo", + }), + ), + *Service( + ServiceLabels(map[string]string{ + "com.docker.stack.namespace": "service-name-2-foo", + }), + ), + }, + }, + } + + for _, uc := range usecases { + buf := new(bytes.Buffer) + cmd := newListCommand(test.NewFakeCliWithOutput(&fakeClient{ + serviceListFunc: func(options types.ServiceListOptions) ([]swarm.Service, error) { + return uc.swarmServices, nil + }, + }, buf)) + assert.NoError(t, cmd.Execute()) + actual := buf.String() + expected := golden.Get(t, []byte(actual), uc.golden) + testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected)) + } } diff --git a/components/cli/cli/command/stack/testdata/stack-list-sort-natural.golden b/components/cli/cli/command/stack/testdata/stack-list-sort-natural.golden new file mode 100644 index 0000000000..09507fbf40 --- /dev/null +++ b/components/cli/cli/command/stack/testdata/stack-list-sort-natural.golden @@ -0,0 +1,4 @@ +NAME SERVICES +service-name-1-foo 1 +service-name-2-foo 1 +service-name-10-foo 1 diff --git a/components/cli/cli/command/system/client_test.go b/components/cli/cli/command/system/client_test.go new file mode 100644 index 0000000000..ed0e803ea8 --- /dev/null +++ b/components/cli/cli/command/system/client_test.go @@ -0,0 +1,15 @@ +package system + +import ( + "github.com/docker/docker/client" +) + +type fakeClient struct { + client.Client + + version string +} + +func (cli *fakeClient) ClientVersion() string { + return cli.version +} diff --git a/components/cli/cli/command/system/cmd.go b/components/cli/cli/command/system/cmd.go index 455387cd46..60979e7224 100644 --- a/components/cli/cli/command/system/cmd.go +++ b/components/cli/cli/command/system/cmd.go @@ -18,8 +18,8 @@ func NewSystemCommand(dockerCli *command.DockerCli) *cobra.Command { cmd.AddCommand( NewEventsCommand(dockerCli), NewInfoCommand(dockerCli), - NewDiskUsageCommand(dockerCli), - NewPruneCommand(dockerCli), + newDiskUsageCommand(dockerCli), + newPruneCommand(dockerCli), ) return cmd diff --git a/components/cli/cli/command/system/df.go b/components/cli/cli/command/system/df.go index d8f9bdc021..5146482f0e 100644 --- a/components/cli/cli/command/system/df.go +++ b/components/cli/cli/command/system/df.go @@ -15,8 +15,8 @@ type diskUsageOptions struct { format string } -// NewDiskUsageCommand creates a new cobra.Command for `docker df` -func NewDiskUsageCommand(dockerCli *command.DockerCli) *cobra.Command { +// newDiskUsageCommand creates a new cobra.Command for `docker df` +func newDiskUsageCommand(dockerCli *command.DockerCli) *cobra.Command { var opts diskUsageOptions cmd := &cobra.Command{ diff --git a/components/cli/cli/command/system/prune.go b/components/cli/cli/command/system/prune.go index e9383e43d3..c0bc434fb0 100644 --- a/components/cli/cli/command/system/prune.go +++ b/components/cli/cli/command/system/prune.go @@ -7,23 +7,28 @@ import ( "github.com/docker/cli/cli" "github.com/docker/cli/cli/command" - "github.com/docker/cli/cli/command/prune" + "github.com/docker/cli/cli/command/container" + "github.com/docker/cli/cli/command/image" + "github.com/docker/cli/cli/command/network" + "github.com/docker/cli/cli/command/volume" "github.com/docker/cli/opts" + "github.com/docker/docker/api/types/versions" units "github.com/docker/go-units" "github.com/spf13/cobra" "golang.org/x/net/context" ) type pruneOptions struct { - force bool - all bool - pruneVolumes bool - filter opts.FilterOpt + force bool + all bool + pruneBuildCache bool + pruneVolumes bool + filter opts.FilterOpt } -// NewPruneCommand creates a new cobra.Command for `docker prune` -func NewPruneCommand(dockerCli command.Cli) *cobra.Command { - options := pruneOptions{filter: opts.NewFilterOpt()} +// newPruneCommand creates a new cobra.Command for `docker prune` +func newPruneCommand(dockerCli command.Cli) *cobra.Command { + options := pruneOptions{filter: opts.NewFilterOpt(), pruneBuildCache: true} cmd := &cobra.Command{ Use: "prune [OPTIONS]", @@ -52,20 +57,38 @@ const confirmationTemplate = `WARNING! This will remove: {{- end }} Are you sure you want to continue?` +// runBuildCachePrune executes a prune command for build cache +func runBuildCachePrune(dockerCli command.Cli, _ opts.FilterOpt) (uint64, string, error) { + report, err := dockerCli.Client().BuildCachePrune(context.Background()) + if err != nil { + return 0, "", err + } + return report.SpaceReclaimed, "", nil +} + func runPrune(dockerCli command.Cli, options pruneOptions) error { + if versions.LessThan(dockerCli.Client().ClientVersion(), "1.31") { + options.pruneBuildCache = false + } if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), confirmationMessage(options)) { return nil } - - var spaceReclaimed uint64 + imagePrune := func(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error) { + return image.RunPrune(dockerCli, options.all, options.filter) + } pruneFuncs := []func(dockerCli command.Cli, filter opts.FilterOpt) (uint64, string, error){ - prune.RunContainerPrune, - prune.RunNetworkPrune, + container.RunPrune, + network.RunPrune, } if options.pruneVolumes { - pruneFuncs = append(pruneFuncs, prune.RunVolumePrune) + pruneFuncs = append(pruneFuncs, volume.RunPrune) + } + pruneFuncs = append(pruneFuncs, imagePrune) + if options.pruneBuildCache { + pruneFuncs = append(pruneFuncs, runBuildCachePrune) } + var spaceReclaimed uint64 for _, pruneFn := range pruneFuncs { spc, output, err := pruneFn(dockerCli, options.filter) if err != nil { @@ -77,21 +100,6 @@ func runPrune(dockerCli command.Cli, options pruneOptions) error { } } - spc, output, err := prune.RunImagePrune(dockerCli, options.all, options.filter) - if err != nil { - return err - } - if spc > 0 { - spaceReclaimed += spc - fmt.Fprintln(dockerCli.Out(), output) - } - - report, err := dockerCli.Client().BuildCachePrune(context.Background()) - if err != nil { - return err - } - spaceReclaimed += report.SpaceReclaimed - fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed))) return nil @@ -113,7 +121,9 @@ func confirmationMessage(options pruneOptions) string { } else { warnings = append(warnings, "all dangling images") } - warnings = append(warnings, "all build cache") + if options.pruneBuildCache { + warnings = append(warnings, "all build cache") + } var buffer bytes.Buffer t.Execute(&buffer, &warnings) diff --git a/components/cli/cli/command/system/prune_test.go b/components/cli/cli/command/system/prune_test.go new file mode 100644 index 0000000000..669166c998 --- /dev/null +++ b/components/cli/cli/command/system/prune_test.go @@ -0,0 +1,15 @@ +package system + +import ( + "testing" + + "github.com/docker/cli/cli/internal/test" + "github.com/stretchr/testify/assert" +) + +func TestPrunePromptPre131(t *testing.T) { + cli := test.NewFakeCli(&fakeClient{version: "1.30"}) + cmd := newPruneCommand(cli) + assert.NoError(t, cmd.Execute()) + assert.NotContains(t, cli.OutBuffer().String(), "all build cache") +} diff --git a/components/cli/contrib/completion/bash/docker b/components/cli/contrib/completion/bash/docker index 95cc588ca0..c207d018bd 100644 --- a/components/cli/contrib/completion/bash/docker +++ b/components/cli/contrib/completion/bash/docker @@ -1,4 +1,13 @@ #!/usr/bin/env bash +# shellcheck disable=SC2016,SC2119,SC2155 +# +# Shellcheck ignore list: +# - SC2016: Expressions don't expand in single quotes, use double quotes for that. +# - SC2119: Use foo "$@" if function's $1 should mean script's $1. +# - SC2155: Declare and assign separately to avoid masking return values. +# +# You can find more details for each warning at the following page: +# https://github.com/koalaman/shellcheck/wiki/ # # bash completion file for core docker commands # @@ -101,6 +110,7 @@ __docker_complete_containers_all() { __docker_complete_containers "$@" --all } +# shellcheck disable=SC2120 __docker_complete_containers_removable() { __docker_complete_containers "$@" --filter status=created --filter status=exited } @@ -109,10 +119,12 @@ __docker_complete_containers_running() { __docker_complete_containers "$@" --filter status=running } +# shellcheck disable=SC2120 __docker_complete_containers_stopped() { __docker_complete_containers "$@" --filter status=exited } +# shellcheck disable=SC2120 __docker_complete_containers_unpauseable() { __docker_complete_containers "$@" --filter status=paused } @@ -213,6 +225,7 @@ __docker_complete_networks() { COMPREPLY=( $(compgen -W "$(__docker_networks "$@")" -- "$current") ) } +# shellcheck disable=SC2128,SC2178 __docker_complete_containers_in_network() { local containers=$(__docker_q network inspect -f '{{range $i, $c := .Containers}}{{$i}} {{$c.Name}} {{end}}' "$1") COMPREPLY=( $(compgen -W "$containers" -- "$cur") ) @@ -271,6 +284,7 @@ __docker_plugins_bundled() { for del in "${remove[@]}" ; do plugins=(${plugins[@]/$del/}) done + # shellcheck disable=SC2145 echo "${plugins[@]} ${add[@]}" } @@ -414,7 +428,7 @@ __docker_nodes() { esac done - echo $(__docker_q node ls "$@" | tr -d '*' | awk "NR>1 {print $fields}") "${add[@]}" + echo "$(__docker_q node ls "$@" | tr -d '*' | awk "NR>1 {print $fields}")" "${add[@]}" } # __docker_complete_nodes applies completion of nodes based on the current @@ -469,6 +483,7 @@ __docker_tasks() { } # __docker_complete_services_and_tasks applies completion of services and task IDs. +# shellcheck disable=SC2120 __docker_complete_services_and_tasks() { COMPREPLY=( $(compgen -W "$(__docker_services "$@") $(__docker_tasks)" -- "$cur") ) } @@ -509,7 +524,7 @@ __docker_pos_first_nonflag() { local argument_flags=$1 local counter=$((${subcommand_pos:-${command_pos}} + 1)) - while [ $counter -le $cword ]; do + while [ "$counter" -le "$cword" ]; do if [ -n "$argument_flags" ] && eval "case '${words[$counter]}' in $argument_flags) true ;; *) false ;; esac"; then (( counter++ )) # eat "=" in case of --option=arg syntax @@ -569,10 +584,10 @@ __docker_value_of_option() { local option_extglob=$(__docker_to_extglob "$1") local counter=$((command_pos + 1)) - while [ $counter -lt $cword ]; do + while [ "$counter" -lt "$cword" ]; do case ${words[$counter]} in $option_extglob ) - echo ${words[$counter + 1]} + echo "${words[$counter + 1]}" break ;; esac @@ -609,14 +624,14 @@ __docker_to_extglob() { __docker_subcommands() { local subcommands="$1" - local counter=$(($command_pos + 1)) - while [ $counter -lt $cword ]; do + local counter=$((command_pos + 1)) + while [ "$counter" -lt "$cword" ]; do case "${words[$counter]}" in $(__docker_to_extglob "$subcommands") ) subcommand_pos=$counter local subcommand=${words[$counter]} local completions_func=_docker_${command}_${subcommand//-/_} - declare -F $completions_func >/dev/null && $completions_func + declare -F "$completions_func" >/dev/null && "$completions_func" return 0 ;; esac @@ -951,7 +966,7 @@ __docker_complete_signals() { SIGUSR1 SIGUSR2 ) - COMPREPLY=( $( compgen -W "${signals[*]} ${signals[*]#SIG}" -- "$( echo $cur | tr '[:lower:]' '[:upper:]')" ) ) + COMPREPLY=( $( compgen -W "${signals[*]} ${signals[*]#SIG}" -- "$( echo "$cur" | tr '[:lower:]' '[:upper:]')" ) ) } __docker_complete_user_group() { @@ -991,7 +1006,7 @@ _docker_docker() { ;; *) local counter=$( __docker_pos_first_nonflag "$(__docker_to_extglob "$global_options_with_args")" ) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_daemon_is_experimental && commands+=(${experimental_commands[*]}) COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) ) fi @@ -1044,7 +1059,7 @@ _docker_checkpoint_create() { ;; *) local counter=$(__docker_pos_first_nonflag '--checkpoint-dir') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_running fi ;; @@ -1065,7 +1080,7 @@ _docker_checkpoint_ls() { ;; *) local counter=$(__docker_pos_first_nonflag '--checkpoint-dir') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_all fi ;; @@ -1086,9 +1101,9 @@ _docker_checkpoint_rm() { ;; *) local counter=$(__docker_pos_first_nonflag '--checkpoint-dir') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_all - elif [ $cword -eq $(($counter + 1)) ]; then + elif [ "$cword" -eq "$((counter + 1))" ]; then COMPREPLY=( $( compgen -W "$(__docker_q checkpoint ls "$prev" | sed 1d)" -- "$cur" ) ) fi ;; @@ -1149,7 +1164,7 @@ _docker_container_attach() { ;; *) local counter=$(__docker_pos_first_nonflag '--detach-keys') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_running fi ;; @@ -1170,13 +1185,13 @@ _docker_container_commit() { *) local counter=$(__docker_pos_first_nonflag '--author|-a|--change|-c|--message|-m') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_all return fi (( counter++ )) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_image_repos_and_tags return fi @@ -1191,7 +1206,7 @@ _docker_container_cp() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then case "$cur" in *:) return @@ -1206,6 +1221,7 @@ _docker_container_cp() { local containers=( ${COMPREPLY[@]} ) COMPREPLY=( $( compgen -W "${files[*]} ${containers[*]}" -- "$cur" ) ) + # shellcheck disable=SC2128 if [[ "$COMPREPLY" == *: ]]; then __docker_nospace fi @@ -1215,7 +1231,7 @@ _docker_container_cp() { fi (( counter++ )) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then if [ -e "$prev" ]; then __docker_complete_containers_all COMPREPLY=( $( compgen -W "${COMPREPLY[*]}" -S ':' ) ) @@ -1240,7 +1256,7 @@ _docker_container_diff() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_all fi ;; @@ -1252,7 +1268,7 @@ _docker_container_exec() { case "$prev" in --env|-e) - # we do not append a "=" here because "-e VARNAME" is legal systax, too + # we do not append a "=" here because "-e VARNAME" is legal syntax, too COMPREPLY=( $( compgen -e -- "$cur" ) ) __docker_nospace return @@ -1287,7 +1303,7 @@ _docker_container_export() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_all fi ;; @@ -1329,7 +1345,7 @@ _docker_container_logs() { ;; *) local counter=$(__docker_pos_first_nonflag '--since|--tail') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_all fi ;; @@ -1425,7 +1441,7 @@ _docker_container_port() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_all fi ;; @@ -1459,7 +1475,7 @@ _docker_container_rename() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_all fi ;; @@ -1608,7 +1624,7 @@ _docker_container_run_and_create() { --tty -t " - if [ "$command" = "run" -o "$subcommand" = "run" ] ; then + if [ "$command" = "run" ] || [ "$subcommand" = "run" ] ; then options_with_args="$options_with_args --detach-keys " @@ -1686,7 +1702,7 @@ _docker_container_run_and_create() { return ;; --env|-e) - # we do not append a "=" here because "-e VARNAME" is legal systax, too + # we do not append a "=" here because "-e VARNAME" is legal syntax, too COMPREPLY=( $( compgen -e -- "$cur" ) ) __docker_nospace return @@ -1699,6 +1715,7 @@ _docker_container_run_and_create() { ;; *) COMPREPLY=( $( compgen -W 'host container:' -- "$cur" ) ) + # shellcheck disable=SC2128 if [ "$COMPREPLY" = "container:" ]; then __docker_nospace fi @@ -1753,6 +1770,7 @@ _docker_container_run_and_create() { ;; *) COMPREPLY=( $( compgen -W 'host container:' -- "$cur" ) ) + # shellcheck disable=SC2128 if [ "$COMPREPLY" = "container:" ]; then __docker_nospace fi @@ -1806,8 +1824,8 @@ _docker_container_run_and_create() { COMPREPLY=( $( compgen -W "$all_options" -- "$cur" ) ) ;; *) - local counter=$( __docker_pos_first_nonflag $( __docker_to_alternatives "$options_with_args" ) ) - if [ $cword -eq $counter ]; then + local counter=$( __docker_pos_first_nonflag "$( __docker_to_alternatives "$options_with_args" )" ) + if [ "$cword" -eq "$counter" ]; then __docker_complete_images fi ;; @@ -1816,7 +1834,7 @@ _docker_container_run_and_create() { _docker_container_start() { __docker_complete_detach_keys && return - + # shellcheck disable=SC2078 case "$prev" in --checkpoint) if [ __docker_daemon_is_experimental ] ; then @@ -1884,7 +1902,7 @@ _docker_container_top() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_running fi ;; @@ -1898,7 +1916,7 @@ _docker_container_unpause() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_containers_unpauseable fi ;; @@ -2098,7 +2116,7 @@ _docker_daemon() { return ;; --storage-driver|-s) - COMPREPLY=( $( compgen -W "aufs btrfs devicemapper overlay overlay2 vfs zfs" -- "$(echo $cur | tr '[:upper:]' '[:lower:]')" ) ) + COMPREPLY=( $( compgen -W "aufs btrfs devicemapper overlay overlay2 vfs zfs" -- "$(echo "$cur" | tr '[:upper:]' '[:lower:]')" ) ) return ;; --storage-opt) @@ -2211,7 +2229,7 @@ _docker_export() { _docker_help() { local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then COMPREPLY=( $( compgen -W "${commands[*]}" -- "$cur" ) ) fi } @@ -2348,8 +2366,8 @@ _docker_image_build() { COMPREPLY=( $( compgen -W "$all_options" -- "$cur" ) ) ;; *) - local counter=$( __docker_pos_first_nonflag $( __docker_to_alternatives "$options_with_args" ) ) - if [ $cword -eq $counter ]; then + local counter=$( __docker_pos_first_nonflag "$( __docker_to_alternatives "$options_with_args" )" ) + if [ "$cword" -eq "$counter" ]; then _filedir -d fi ;; @@ -2369,7 +2387,7 @@ _docker_image_history() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_images fi ;; @@ -2393,12 +2411,12 @@ _docker_image_import() { ;; *) local counter=$(__docker_pos_first_nonflag '--change|-c|--message|-m') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then return fi (( counter++ )) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_image_repos_and_tags return fi @@ -2493,7 +2511,7 @@ _docker_image_pull() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then for arg in "${COMP_WORDS[@]}"; do case "$arg" in --all-tags|-a) @@ -2515,7 +2533,7 @@ _docker_image_push() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_image_repos_and_tags fi ;; @@ -2567,13 +2585,13 @@ _docker_image_tag() { *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_image_repos_and_tags return fi (( counter++ )) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_image_repos_and_tags return fi @@ -2737,10 +2755,10 @@ _docker_network_connect() { COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) ) ;; *) - local counter=$( __docker_pos_first_nonflag $( __docker_to_alternatives "$options_with_args" ) ) - if [ $cword -eq $counter ]; then + local counter=$( __docker_pos_first_nonflag "$( __docker_to_alternatives "$options_with_args" )" ) + if [ "$cword" -eq "$counter" ]; then __docker_complete_networks - elif [ $cword -eq $(($counter + 1)) ]; then + elif [ "$cword" -eq "$((counter + 1))" ]; then __docker_complete_containers_all fi ;; @@ -2788,9 +2806,9 @@ _docker_network_disconnect() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_networks - elif [ $cword -eq $(($counter + 1)) ]; then + elif [ "$cword" -eq "$((counter + 1))" ]; then __docker_complete_containers_in_network "$prev" fi ;; @@ -2969,7 +2987,7 @@ _docker_service_logs() { ;; *) local counter=$(__docker_pos_first_nonflag '--since|--tail') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_services_and_tasks fi ;; @@ -3076,7 +3094,7 @@ _docker_service_ps() { ;; *) local counter=$(__docker_pos_first_nonflag '--filter|-f') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_services fi ;; @@ -3091,6 +3109,7 @@ _docker_service_update() { # and `docker service update` _docker_service_update_and_create() { local options_with_args=" + --credential-spec --endpoint-mode --entrypoint --env -e @@ -3321,13 +3340,13 @@ _docker_service_update_and_create() { COMPREPLY=( $( compgen -W "$boolean_options $options_with_args" -- "$cur" ) ) ;; *) - local counter=$( __docker_pos_first_nonflag $( __docker_to_alternatives "$options_with_args" ) ) + local counter=$( __docker_pos_first_nonflag "$( __docker_to_alternatives "$options_with_args" )" ) if [ "$subcommand" = "update" ] ; then - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_services fi else - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_images fi fi @@ -3466,7 +3485,7 @@ _docker_swarm_join_token() { ;; *) local counter=$( __docker_pos_first_nonflag ) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then COMPREPLY=( $( compgen -W "manager worker" -- "$cur" ) ) fi ;; @@ -3685,7 +3704,7 @@ _docker_node_update() { ;; *) local counter=$(__docker_pos_first_nonflag '--availability|--label-add|--label-rm|--role') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_nodes fi ;; @@ -3732,10 +3751,10 @@ _docker_plugin_create() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then # reponame return - elif [ $cword -eq $((counter + 1)) ]; then + elif [ "$cword" -eq "$((counter + 1))" ]; then _filedir -d fi ;; @@ -3749,7 +3768,7 @@ _docker_plugin_disable() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_plugins_installed --filter enabled=true fi ;; @@ -3769,7 +3788,7 @@ _docker_plugin_enable() { ;; *) local counter=$(__docker_pos_first_nonflag '--timeout') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_plugins_installed --filter enabled=false fi ;; @@ -3849,7 +3868,7 @@ _docker_plugin_push() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_plugins_installed fi ;; @@ -3878,7 +3897,7 @@ _docker_plugin_set() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_plugins_installed fi ;; @@ -3892,10 +3911,10 @@ _docker_plugin_upgrade() { ;; *) local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_plugins_installed __ltrim_colon_completions "$cur" - elif [ $cword -eq $((counter + 1)) ]; then + elif [ "$cword" -eq "$((counter + 1))" ]; then local plugin_images="$(__docker_plugins_installed)" COMPREPLY=( $(compgen -S : -W "${plugin_images%:*}" -- "$cur") ) __docker_nospace @@ -4189,7 +4208,7 @@ _docker_stack_ps() { ;; *) local counter=$(__docker_pos_first_nonflag '--filter|-f') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_stacks fi ;; @@ -4243,7 +4262,7 @@ _docker_stack_services() { ;; *) local counter=$(__docker_pos_first_nonflag '--filter|-f|--format') - if [ $cword -eq $counter ]; then + if [ "$cword" -eq "$counter" ]; then __docker_complete_stacks fi ;; @@ -4685,7 +4704,7 @@ _docker() { local command='docker' command_pos=0 subcommand_pos local counter=1 - while [ $counter -lt $cword ]; do + while [ "$counter" -lt "$cword" ]; do case "${words[$counter]}" in # save host so that completion can use custom daemon --host|-H) diff --git a/components/cli/docker.Makefile b/components/cli/docker.Makefile index 2d7474b583..9b958397a1 100644 --- a/components/cli/docker.Makefile +++ b/components/cli/docker.Makefile @@ -7,6 +7,7 @@ DEV_DOCKER_IMAGE_NAME = docker-cli-dev LINTER_IMAGE_NAME = docker-cli-lint CROSS_IMAGE_NAME = docker-cli-cross +VALIDATE_IMAGE_NAME = docker-cli-shell-validate MOUNTS = -v "$(CURDIR)":/go/src/github.com/docker/cli VERSION = $(shell cat VERSION) ENVVARS = -e VERSION=$(VERSION) -e GITCOMMIT @@ -25,6 +26,9 @@ build_linter_image: build_cross_image: docker build ${DOCKER_BUILD_ARGS} -t $(CROSS_IMAGE_NAME) -f ./dockerfiles/Dockerfile.cross . +.PHONY: build_shell_validate_image +build_shell_validate_image: + docker build -t $(VALIDATE_IMAGE_NAME) -f ./dockerfiles/Dockerfile.shellcheck . # build executable using a container binary: build_docker_image @@ -80,3 +84,7 @@ manpages: build_docker_image .PHONY: yamldocs yamldocs: build_docker_image docker run -ti --rm $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make yamldocs + +.PHONY: shellcheck +shellcheck: build_shell_validate_image + docker run -ti --rm $(MOUNTS) $(VALIDATE_IMAGE_NAME) make shellcheck diff --git a/components/cli/dockerfiles/Dockerfile.dev b/components/cli/dockerfiles/Dockerfile.dev index a80d3418f1..7f9482e612 100644 --- a/components/cli/dockerfiles/Dockerfile.dev +++ b/components/cli/dockerfiles/Dockerfile.dev @@ -4,21 +4,21 @@ FROM golang:1.8.3-alpine RUN apk add -U git make bash coreutils ARG VNDR_SHA=9909bb2b8a0b7ea464527b376dc50389c90df587 -RUN go get github.com/LK4D4/vndr && \ +RUN go get -d github.com/LK4D4/vndr && \ cd /go/src/github.com/LK4D4/vndr && \ git checkout -q "$VNDR_SHA" && \ go build -v -o /usr/bin/vndr . && \ rm -rf /go/src/* /go/pkg/* /go/bin/* ARG BINDATA_SHA=a0ff2567cfb70903282db057e799fd826784d41d -RUN go get github.com/jteeuwen/go-bindata/go-bindata && \ +RUN go get -d github.com/jteeuwen/go-bindata/go-bindata && \ cd /go/src/github.com/jteeuwen/go-bindata/go-bindata && \ git checkout -q "$BINDATA_SHA" && \ go build -v -o /usr/bin/go-bindata . && \ rm -rf /go/src/* /go/pkg/* /go/bin/* ARG FILEWATCHER_SHA=2e12ea42f6c8c089b19e992145bb94e8adaecedb -RUN go get github.com/dnephin/filewatcher && \ +RUN go get -d github.com/dnephin/filewatcher && \ cd /go/src/github.com/dnephin/filewatcher && \ git checkout -q "$FILEWATCHER_SHA" && \ go build -v -o /usr/bin/filewatcher . && \ diff --git a/components/cli/dockerfiles/Dockerfile.lint b/components/cli/dockerfiles/Dockerfile.lint index 0b9385e0d1..c981efbadc 100644 --- a/components/cli/dockerfiles/Dockerfile.lint +++ b/components/cli/dockerfiles/Dockerfile.lint @@ -3,7 +3,7 @@ FROM golang:1.8.3-alpine RUN apk add -U git ARG GOMETALINTER_SHA=4306381615a2ba2a207f8fcea02c08c6b2b0803f -RUN go get github.com/alecthomas/gometalinter && \ +RUN go get -d github.com/alecthomas/gometalinter && \ cd /go/src/github.com/alecthomas/gometalinter && \ git checkout -q "$GOMETALINTER_SHA" && \ go build -v -o /usr/local/bin/gometalinter . && \ diff --git a/components/cli/dockerfiles/Dockerfile.shellcheck b/components/cli/dockerfiles/Dockerfile.shellcheck new file mode 100644 index 0000000000..12f665f7a7 --- /dev/null +++ b/components/cli/dockerfiles/Dockerfile.shellcheck @@ -0,0 +1,9 @@ +FROM debian:stretch-slim + +RUN apt-get update && \ + apt-get -y install make shellcheck && \ + apt-get clean + +WORKDIR /go/src/github.com/docker/cli + +CMD bash diff --git a/components/cli/docs/reference/builder.md b/components/cli/docs/reference/builder.md index 65f6e05f04..16c1b48d31 100644 --- a/components/cli/docs/reference/builder.md +++ b/components/cli/docs/reference/builder.md @@ -530,15 +530,16 @@ FROM extras:${CODE_VERSION} CMD /code/run-extras ``` -To use the default value of an `ARG` declared before the first `FROM` use an -`ARG` instruction without a value: +An `ARG` declared before a `FROM` is outside of a build stage, so it +can't be used in any instruction after a `FROM`. To use the default value of +an `ARG` declared before the first `FROM` use an `ARG` instruction without +a value inside of a build stage: ```Dockerfile -ARG SETTINGS=default - -FROM busybox -ARG SETTINGS - +ARG VERSION=latest +FROM busybox:$VERSION +ARG VERSION +RUN echo $VERSION > image_version ``` ## RUN @@ -1364,8 +1365,8 @@ defined in the Dockerfile, the build outputs a warning. [Warning] One or more build-args [foo] were not consumed. ``` -The Dockerfile author can define a single variable by specifying `ARG` once or many -variables by specifying `ARG` more than once. For example, a valid Dockerfile: +A Dockerfile may include one or more `ARG` instructions. For example, +the following is a valid Dockerfile: ``` FROM busybox @@ -1374,7 +1375,13 @@ ARG buildno ... ``` -A Dockerfile author may optionally specify a default value for an `ARG` instruction: +> **Warning:** It is not recommended to use build-time variables for +> passing secrets like github keys, user credentials etc. Build-time variable +> values are visible to any user of the image with the `docker history` command. + +### Default values + +An `ARG` instruction can optionally include a default value: ``` FROM busybox @@ -1383,8 +1390,10 @@ ARG buildno=1 ... ``` -If an `ARG` value has a default and if there is no value passed at build-time, the -builder uses the default. +If an `ARG` instruction has a default value and if there is no value passed +at build-time, the builder uses the default. + +### Scope An `ARG` variable definition comes into effect from the line on which it is defined in the `Dockerfile` not from the argument's use on the command-line or @@ -1408,9 +1417,21 @@ subsequent line 3. The `USER` at line 4 evaluates to `what_user` as `user` is defined and the `what_user` value was passed on the command line. Prior to its definition by an `ARG` instruction, any use of a variable results in an empty string. -> **Warning:** It is not recommended to use build-time variables for -> passing secrets like github keys, user credentials etc. Build-time variable -> values are visible to any user of the image with the `docker history` command. +An `ARG` instruction goes out of scope at the end of the build +stage where it was defined. To use an arg in multiple stages, each stage must +include the `ARG` instruction. + +``` +FROM busybox +ARG SETTINGS +RUN ./run/setup $SETTINGS + +FROM busybox +ARG SETTINGS +RUN ./run/other $SETTINGS +``` + +### Using ARG variables You can use an `ARG` or an `ENV` instruction to specify variables that are available to the `RUN` instruction. Environment variables defined using the @@ -1459,6 +1480,8 @@ from the command line and persist them in the final image by leveraging the `ENV` instruction. Variable expansion is only supported for [a limited set of Dockerfile instructions.](#environment-replacement) +### Predefined ARGs + Docker has a set of predefined `ARG` variables that you can use without a corresponding `ARG` instruction in the Dockerfile. diff --git a/components/cli/docs/reference/commandline/events.md b/components/cli/docs/reference/commandline/events.md index 71475b43ec..65b4f29013 100644 --- a/components/cli/docs/reference/commandline/events.md +++ b/components/cli/docs/reference/commandline/events.md @@ -80,9 +80,9 @@ Docker images report the following events: Docker plugins report the following events: -- `install` - `enable` - `disable` +- `install` - `remove` #### Volumes @@ -90,9 +90,9 @@ Docker plugins report the following events: Docker volumes report the following events: - `create` +- `destroy` - `mount` - `unmount` -- `destroy` #### Networks @@ -100,8 +100,9 @@ Docker networks report the following events: - `create` - `connect` -- `disconnect` - `destroy` +- `disconnect` +- `remove` #### Daemons @@ -109,6 +110,38 @@ Docker daemons report the following events: - `reload` +#### Services + +Docker services report the following events: + +- `create` +- `remove` +- `update` + +#### Nodes + +Docker nodes report the following events: + +- `create` +- `remove` +- `update` + +#### Secrets + +Docker secrets report the following events: + +- `create` +- `remove` +- `update` + +#### Configs + +Docker configs report the following events: + +- `create` +- `remove` +- `update` + ### Limiting, filtering, and formatting the output #### Limit events by time @@ -149,7 +182,8 @@ The currently supported filters are: * label (`label=` or `label==`) * network (`network=`) * plugin (`plugin=`) -* type (`type=`) +* scope (`scope=`) +* type (`type=`) * volume (`volume=`) #### Format @@ -317,6 +351,29 @@ $ docker events --filter 'type=plugin' 2016-07-25T17:30:14.825557616Z plugin pull ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest) 2016-07-25T17:30:14.888127370Z plugin enable ec7b87f2ce84330fe076e666f17dfc049d2d7ae0b8190763de94e1f2d105993f (name=tiborvass/sample-volume-plugin:latest) + +$ docker events -f type=service + +2017-07-12T06:34:07.999446625Z service create wj64st89fzgchxnhiqpn8p4oj (name=reverent_albattani) +2017-07-12T06:34:21.405496207Z service remove wj64st89fzgchxnhiqpn8p4oj (name=reverent_albattani) + +$ docker events -f type=node + +2017-07-12T06:21:51.951586759Z node update 3xyz5ttp1a253q74z1thwywk9 (name=ip-172-31-23-42, state.new=ready, state.old=unknown) + +$ docker events -f type=secret + +2017-07-12T06:32:13.915704367Z secret create s8o6tmlnndrgzbmdilyy5ymju (name=new_secret) +2017-07-12T06:32:37.052647783Z secret remove s8o6tmlnndrgzbmdilyy5ymju (name=new_secret) + +$ docker events -f type=config +2017-07-12T06:44:13.349037127Z config create u96zlvzdfsyb9sg4mhyxfh3rl (name=abc) +2017-07-12T06:44:36.327694184Z config remove u96zlvzdfsyb9sg4mhyxfh3rl (name=abc) + +$ docker events --filter 'scope=swarm' + +2017-07-10T07:46:50.250024503Z service create m8qcxu8081woyof7w3jaax6gk (name=affectionate_wilson) +2017-07-10T07:47:31.093797134Z secret create 6g5pufzsv438p9tbvl9j94od4 (name=new_secret) ``` ### Format the output diff --git a/components/cli/docs/reference/commandline/search.md b/components/cli/docs/reference/commandline/search.md index f645c78603..83a44251dd 100644 --- a/components/cli/docs/reference/commandline/search.md +++ b/components/cli/docs/reference/commandline/search.md @@ -94,7 +94,6 @@ radial/busyboxplus Full-chain, Internet enabled, busybox made from scratch. Co The flag `--limit` is the maximum number of results returned by a search. This value could be in the range between 1 and 100. The default value of `--limit` is 25. - ### Filtering The filtering flag (`-f` or `--filter`) format is a `key=value` pair. If there is more @@ -103,9 +102,8 @@ than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bi The currently supported filters are: * stars (int - number of stars the image has) -* is-automated (true|false) - is the image automated or not -* is-official (true|false) - is the image official or not - +* is-automated (boolean - true or false) - is the image automated or not +* is-official (boolean - true or false) - is the image official or not #### stars @@ -121,7 +119,6 @@ progrium/busybox 50 radial/busyboxplus Full-chain, Internet enabled, busybox made... 8 [OK] ``` - #### is-automated This example displays images with a name containing 'busybox' diff --git a/components/cli/docs/reference/commandline/service_create.md b/components/cli/docs/reference/commandline/service_create.md index a1b6d18f9d..d09a6bf8e6 100644 --- a/components/cli/docs/reference/commandline/service_create.md +++ b/components/cli/docs/reference/commandline/service_create.md @@ -23,6 +23,7 @@ Create a new service Options: --constraint list Placement constraints --container-label list Container labels + --credential-spec Credential spec for managed service account (Windows only) -d, --detach Exit immediately instead of waiting for the service to converge (default true) --dns list Set custom DNS servers --dns-option list Set DNS options @@ -779,6 +780,24 @@ $ docker service create --name dns-cache -p 53:53/tcp -p 53:53/udp dns-cache $ docker service create --name dns-cache -p 53:53/udp dns-cache ``` +### Provide credential specs for managed service accounts (Windows only) + +This option is only used for services using Windows containers. The +`--credential-spec` must be in the format `file://` or +`registry://`. + +When using the `file://` format, the referenced file must be +present in the `CredentialSpecs` subdirectory in the docker data directory, +which defaults to `C:\ProgramData\Docker\` on Windows. For example, +specifying `file://spec.json` loads `C:\ProgramData\Docker\CredentialSpecs\spec.json`. + +When using the `registry://` format, the credential spec is +read from the Windows registry on the daemon's host. The specified +registry value must be located in: + + HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs + + ### Create services using templates You can use templates for some flags of `service create`, using the syntax diff --git a/components/cli/docs/reference/commandline/service_update.md b/components/cli/docs/reference/commandline/service_update.md index 93c5750eee..8f075d2c19 100644 --- a/components/cli/docs/reference/commandline/service_update.md +++ b/components/cli/docs/reference/commandline/service_update.md @@ -26,6 +26,7 @@ Options: --constraint-rm list Remove a constraint --container-label-add list Add or update a container label --container-label-rm list Remove a container label by its key + --credential-spec Credential spec for managed service account (Windows only) -d, --detach Exit immediately instead of waiting for the service to converge (default true) --dns-add list Add or update a custom DNS server --dns-option-add list Add or update a DNS option diff --git a/components/cli/scripts/validate/shellcheck b/components/cli/scripts/validate/shellcheck new file mode 100755 index 0000000000..f14d1ab04d --- /dev/null +++ b/components/cli/scripts/validate/shellcheck @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +# Maintain an array of files to shellcheck not the best solution but will do for the time being +FILES=() +FILES+=("contrib/completion/bash/docker") +FILES+=("scripts/validate/shellcheck") + +for f in "${FILES[@]}"; do + shellcheck "$f" +done diff --git a/components/cli/vendor.conf b/components/cli/vendor.conf index a212dd0a3a..a71e77c0c0 100755 --- a/components/cli/vendor.conf +++ b/components/cli/vendor.conf @@ -52,3 +52,4 @@ gopkg.in/yaml.v2 4c78c975fe7c825c6d1466c42be594d1d6f3aba6 github.com/tonistiigi/fsutil 0ac4c11b053b9c5c7c47558f81f96c7100ce50fb github.com/stevvooe/continuity cd7a8e21e2b6f84799f5dd4b65faf49c8d3ee02d golang.org/x/sync de49d9dcd27d4f764488181bea099dfe6179bcf0 +vbom.ml/util 928aaa586d7718c70f4090ddf83f2b34c16fdc8d diff --git a/components/cli/vendor/vbom.ml/util/LICENSE b/components/cli/vendor/vbom.ml/util/LICENSE new file mode 100644 index 0000000000..5c695fb590 --- /dev/null +++ b/components/cli/vendor/vbom.ml/util/LICENSE @@ -0,0 +1,17 @@ +The MIT License (MIT) +Copyright (c) 2015 Frits van Bommel +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/components/cli/vendor/vbom.ml/util/README.md b/components/cli/vendor/vbom.ml/util/README.md new file mode 100644 index 0000000000..72de507980 --- /dev/null +++ b/components/cli/vendor/vbom.ml/util/README.md @@ -0,0 +1,5 @@ +## util [![GoDoc](https://godoc.org/vbom.ml/util?status.svg)](https://godoc.org/vbom.ml/util) + + import "vbom.ml/util" + +Go utility packages. diff --git a/components/cli/vendor/vbom.ml/util/sortorder/README.md b/components/cli/vendor/vbom.ml/util/sortorder/README.md new file mode 100644 index 0000000000..ed8da0e29b --- /dev/null +++ b/components/cli/vendor/vbom.ml/util/sortorder/README.md @@ -0,0 +1,5 @@ +## sortorder [![GoDoc](https://godoc.org/vbom.ml/util/sortorder?status.svg)](https://godoc.org/vbom.ml/util/sortorder) + + import "vbom.ml/util/sortorder" + +Sort orders and comparison functions. diff --git a/components/cli/vendor/vbom.ml/util/sortorder/doc.go b/components/cli/vendor/vbom.ml/util/sortorder/doc.go new file mode 100644 index 0000000000..61b37a9374 --- /dev/null +++ b/components/cli/vendor/vbom.ml/util/sortorder/doc.go @@ -0,0 +1,5 @@ +// Package sortorder implements sort orders and comparison functions. +// +// Currently, it only implements so-called "natural order", where integers +// embedded in strings are compared by value. +package sortorder // import "vbom.ml/util/sortorder" diff --git a/components/cli/vendor/vbom.ml/util/sortorder/natsort.go b/components/cli/vendor/vbom.ml/util/sortorder/natsort.go new file mode 100644 index 0000000000..1af08c1bd1 --- /dev/null +++ b/components/cli/vendor/vbom.ml/util/sortorder/natsort.go @@ -0,0 +1,76 @@ +package sortorder + +// Natural implements sort.Interface to sort strings in natural order. This +// means that e.g. "abc2" < "abc12". +// +// Non-digit sequences and numbers are compared separately. The former are +// compared bytewise, while the latter are compared numerically (except that +// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") +// +// Limitation: only ASCII digits (0-9) are considered. +type Natural []string + +func (n Natural) Len() int { return len(n) } +func (n Natural) Swap(i, j int) { n[i], n[j] = n[j], n[i] } +func (n Natural) Less(i, j int) bool { return NaturalLess(n[i], n[j]) } + +func isdigit(b byte) bool { return '0' <= b && b <= '9' } + +// NaturalLess compares two strings using natural ordering. This means that e.g. +// "abc2" < "abc12". +// +// Non-digit sequences and numbers are compared separately. The former are +// compared bytewise, while the latter are compared numerically (except that +// the number of leading zeros is used as a tie-breaker, so e.g. "2" < "02") +// +// Limitation: only ASCII digits (0-9) are considered. +func NaturalLess(str1, str2 string) bool { + idx1, idx2 := 0, 0 + for idx1 < len(str1) && idx2 < len(str2) { + c1, c2 := str1[idx1], str2[idx2] + dig1, dig2 := isdigit(c1), isdigit(c2) + switch { + case dig1 != dig2: // Digits before other characters. + return dig1 // True if LHS is a digit, false if the RHS is one. + case !dig1: // && !dig2, because dig1 == dig2 + // UTF-8 compares bytewise-lexicographically, no need to decode + // codepoints. + if c1 != c2 { + return c1 < c2 + } + idx1++ + idx2++ + default: // Digits + // Eat zeros. + for ; idx1 < len(str1) && str1[idx1] == '0'; idx1++ { + } + for ; idx2 < len(str2) && str2[idx2] == '0'; idx2++ { + } + // Eat all digits. + nonZero1, nonZero2 := idx1, idx2 + for ; idx1 < len(str1) && isdigit(str1[idx1]); idx1++ { + } + for ; idx2 < len(str2) && isdigit(str2[idx2]); idx2++ { + } + // If lengths of numbers with non-zero prefix differ, the shorter + // one is less. + if len1, len2 := idx1-nonZero1, idx2-nonZero2; len1 != len2 { + return len1 < len2 + } + // If they're not equal, string comparison is correct. + if nr1, nr2 := str1[nonZero1:idx1], str2[nonZero2:idx2]; nr1 != nr2 { + return nr1 < nr2 + } + // Otherwise, the one with less zeros is less. + // Because everything up to the number is equal, comparing the index + // after the zeros is sufficient. + if nonZero1 != nonZero2 { + return nonZero1 < nonZero2 + } + } + // They're identical so far, so continue comparing. + } + // So far they are identical. At least one is ended. If the other continues, + // it sorts last. + return len(str1) < len(str2) +}