Compare commits

..

160 Commits

Author SHA1 Message Date
29fcd5dfae Merge pull request #81 from andrewhsu/cl
cherry-pick update rpm changelog
2017-06-15 02:46:13 -07:00
9600fb4e78 update rpm changelog
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit 659d6015b6bf8ec3c24cf5377f3eda88beed0a10)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-15 09:44:20 +00:00
0f80f28c1a Merge pull request #80 from andrewhsu/rc4
bump VERSION files to 17.06.0-ce-rc4
2017-06-15 01:18:13 -07:00
29e7f7b8c9 bump VERSION files to 17.06.0-ce-rc4
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-15 08:17:14 +00:00
4c2cf22362 Merge pull request #79 from andrewhsu/systemd
cherry-pick Add systemd scripts
2017-06-15 01:13:42 -07:00
afcb08cfb0 Use internal systemd scripts for DEB packaging
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
(cherry picked from commit 477d0b1f735f7e243425cd2c5f48e8f216556c2f)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-15 08:10:46 +00:00
46a9d6b041 Use internal systemd scripts for RPM packaging
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
(cherry picked from commit cbfe576e680b19c5f4c992e52bee59f2bc540026)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-15 08:10:33 +00:00
b8c7116a23 Add initial systemd scripts
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
(cherry picked from commit 7b31f87ffd8115f8226f68b788f5b36db53981f6)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-15 08:10:17 +00:00
b089783b38 Merge pull request #76 from andrewhsu/pool
cherry-pick container/stream/attach: use pools.Copy
2017-06-14 11:35:08 -07:00
605ffe91cf Merge pull request #75 from aaronlehmann/vendor-swarmkit-bf9b892
Vendor swarmkit bf9b892
2017-06-13 23:23:05 -07:00
eb837fd7bc pkg/pools: add buffer32KPool & use it for copy
Signed-off-by: Cristian Staretu <cristian.staretu@gmail.com>
(cherry picked from commit ba40f4593f79a653f1e3a8b9597b7900fb68a564)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-14 00:20:13 +00:00
475c218fd7 container/stream/attach: use pools.Copy
The use of pools.Copy avoids io.Copy's internal buffer allocation.
This commit replaces io.Copy with pools.Copy to avoid the allocation of
buffers in io.Copy.

Signed-off-by: Cristian Staretu <cristian.staretu@gmail.com>
(cherry picked from commit 014095e6a07748d0e1ce2f759f5c4f4b3783e765)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-14 00:20:13 +00:00
f2eb8b825b Vendor swarmkit bf9b892
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2017-06-13 11:37:41 -07:00
7953dbc649 Merge pull request #72 from andrewhsu/rc3
bump VERSION files to 17.06.0-ce-rc3
2017-06-13 00:27:55 -07:00
5c94f3eeda bump VERSION files to 17.06.0-ce-rc3
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-13 07:27:03 +00:00
d96b9b7edf Merge pull request #68 from mlaventure/17.06-rc3-changelog
Update changelog for 17.06.0-ce-rc3
2017-06-13 00:24:51 -07:00
be1ea9771c added changelog entry for swarmkit
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-13 07:21:48 +00:00
5d8e13a06c Merge pull request #71 from andrewhsu/dep-v1
cherry-pick: Disable legacy (v1) registries by default
2017-06-12 23:21:57 -07:00
d611e35db6 Merge pull request #69 from andrewhsu/sk
revendor github.com/docker/swarmkit to ef3c57a
2017-06-12 22:03:36 -07:00
85aaa4f261 Merge pull request #66 from mavenugo/17.06-ln
Vendoring libnetwork 4f5310be349d9299f6cab6d5822312f00cfa965c
2017-06-12 21:01:20 -07:00
56ecfabb9f Merge pull request #70 from tiborvass/revert-tty-changes
Revert tty changes
2017-06-12 20:53:00 -07:00
97a704b2a7 Merge pull request #52 from seemethere/cherry_pick_33335
Check signal is unset before using user stopsignal
2017-06-12 19:24:57 -07:00
a16a6004a8 revendor github.com/docker/swarmkit to 6083c76
To get the changes:
* https://github.com/docker/swarmkit/pull/2234
* https://github.com/docker/swarmkit/pull/2237
* https://github.com/docker/swarmkit/pull/2238

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-13 00:04:15 +00:00
17c2a50117 Update docs, completion scripts for disable-legacy-registry
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 2b8f0eef7338f37104464154ba65aef7db3b9703)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-12 23:28:29 +00:00
5e671f7b53 Disable legacy (v1) registries by default
Deprecation of interacting with v1 registries was
started in docker 1.8.3, which added a `--disable-legacy-registry`
flag.

This option was anounced to be the default starting
with docker 17.06, and v1 registries completely
removed in docker 17.12.

This patch updates the default, and disables
interaction with v1 registres by default.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 128280013f2ad90520c97b47a787be0db883e870)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-12 23:28:14 +00:00
986443bad2 [cli] vendor: Revert ONCLR and OPOST changes
This reverts to a version of runc without the ONCLR cleared to not cause
a regression with different clients using --tty.

This also reverts the OPOST changes to the term package to support the
initial change.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
(cherry picked from commit a5e83836a49547b2add871bb52cbd8bfedb57114)
Signed-off-by: Tibor Vass <tibor@docker.com>
2017-06-12 23:27:57 +00:00
eaac30972d [engine] Revert ONCLR and OPOST changes
This reverts to a version of runc without the ONCLR cleared to not cause
a regression with different clients using --tty.

This also reverts the OPOST changes to the term package to support the
initial change.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
(cherry picked from commit a5e83836a49547b2add871bb52cbd8bfedb57114)
Signed-off-by: Tibor Vass <tibor@docker.com>
2017-06-12 23:27:48 +00:00
6f00f5603d Merge pull request #59 from seemethere/cherry_pick_33473
Update deprecated.md for removal of --email flag
2017-06-12 15:43:01 -07:00
ad098a7a03 Merge pull request #64 from seemethere/cherry_pick_143
Remove deprecated -e/--email flag from docker login
2017-06-12 15:42:02 -07:00
9c1f95529e Update changelog for 17.06.0-ce-rc3
Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
2017-06-12 11:41:44 -07:00
8c2c3e21a3 Remove "-e" / "--email" from integration tests
This option is no longer supported in docker 17.06,
so should not be used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-06-12 10:18:49 -07:00
da2d17a18f Merge pull request #65 from seemethere/cherry_pick_33609
Merge pull request #29418 from aboch/p66
2017-06-12 09:27:42 -07:00
8ef3d608d9 Vendoring libnetwork 4f5310be349d9299f6cab6d5822312f00cfa965c
This is a cherry-pick of https://github.com/moby/moby/pull/33634
that brings in https://github.com/docker/libnetwork/pull/1796.

Signed-off-by: Madhu Venugopal <madhu@docker.com>
2017-06-12 09:04:36 -07:00
514d89f021 Merge pull request #55 from andrewhsu/revendor-libnetwork-netlink
revendor libnetwork and netlink in engine component
2017-06-11 10:32:17 -07:00
0189f9d259 Merge pull request #57 from seemethere/cherry_pick_33413
Do not reuse a http.Request after a failure in callWithRetry
2017-06-10 22:10:48 -07:00
ca7df6fe42 Merge pull request #61 from seemethere/cherry_pick_33583
Logging driver should receive same file in start/stop request
2017-06-10 22:09:47 -07:00
47c1b00158 Merge pull request #60 from seemethere/cherry_pick_144
Allow --detach and --quiet flags when using --rollback
2017-06-10 15:48:54 -07:00
f7e9f2cefa Merge pull request #58 from seemethere/cherry_pick_33363
Fix cancelling builder on chunked requests
2017-06-10 15:48:02 -07:00
960ddece48 Merge pull request #62 from seemethere/cherry_pick_33598
daemon: Remove daemon datastructure dump functionality
2017-06-10 14:41:21 -07:00
5a5e9e32bd Merge pull request #63 from seemethere/cherry_pick_33606
Backport 17.05.0-ce changelog to master
2017-06-10 13:41:31 -07:00
eff2539693 Merge pull request #44 from nishanttotla/cpick-cli-121
[cherry-pick] Enable client side digest pinning for stack deploy
2017-06-10 13:29:54 -07:00
e92c400a01 Merge pull request #32 from nishanttotla/cpick-moby-33279-17.06
[cherry-pick] vendor: Ensure service images get default tag and print familiar strings
2017-06-10 13:27:49 -07:00
5d4843baa3 Merge pull request #56 from tiborvass/cli-go1.8
Update golang 1.8.3
2017-06-09 18:28:53 -07:00
cfb8aafe5e Merge pull request #42 from mlaventure/17.06-rc2-changelog
Remove unneeded line in 17.06 CHANGELOG
2017-06-09 18:23:18 -07:00
4e1370233e Merge pull request #13 from mlaventure/chp-moby-33279
Ensure service images get default tag and print familiar strings
2017-06-09 17:58:16 -07:00
091732e350 Merge pull request #29418 from aboch/p66
[1.13.x] Fix buildIpamResources()
(cherry picked from commit 4d2be03b68dd54d8f2307e8b4178ad8ed8d5d343)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 27498a3c60018ea2880a18beec65710fd27971a7)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 17:38:11 -07:00
1af533909d Remove deprecated -e/--email flag from docker login
The `docker login -e` / `docker login --email` option was deprecated in
Docker 1.11 (https://github.com/moby/moby/releases/tag/v1.11.0) through
aee260d4eb3aa0fc86ee5038010b7bbc24512ae5 (April 2016), and when used has
been outputing a deprecation warning since;

    Flag --email has been deprecated, will be removed in 17.06.

Originally this option was scheduled to be removed in docker 1.13, but
extended to docker 17.06 due to a change in our deprecation policy.

Given that only docker 1.10 and older use this flag (which is EOL, including
for CS versions, as of February 2017), will now be removed.

With this patch, `docker login` will now produce an Error if the flag
is used.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 8b383d2351)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 17:37:14 -07:00
a014274b80 bump to GA
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
(cherry picked from commit 89658bed64c2a8fe05a978e5b87dbec409d57a0f)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit d94f281d78e85f4c7de6b8347ab95d0afd5b8a8d)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 17:35:56 -07:00
7b65e51031 bump to rc2
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
(cherry picked from commit c57fdb2a14cfba584686ddad909e3006284d10aa)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit b4d36e47c3501541dc66b6336e286b8c11cbcec7)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 17:35:34 -07:00
ab5798a6f7 daemon: Remove daemon datastructure dump functionality
When sending SIGUSR1 to the daemon, it can crash because of a concurrent
map access panic, showing a stack trace involving dumpDaemon. It appears
it's not possible to recover from a concurrent map access panic. Since
it's important that SIGUSR1 not be a destructive operation, sadly the
best course of action I can think of is to remove this functionality.

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
(cherry picked from commit a4c68ee8574c9b8a3309ebebee0d90108042ba61)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 17:34:45 -07:00
899ed1b641 Logging driver should receive same file in start/stop request
Signed-off-by: Peter Bücker <peter.buecker@gmail.com>
(cherry picked from commit e908e1a357b435d7fab497d51cdd3e58458a0590)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 17:33:46 -07:00
a9a1a9c7de Merge pull request #43 from cyli/re-vendor-swarmkit
[17.06] Re-vendor swarmkit
2017-06-09 17:32:55 -07:00
5ed0b2e10c Allow --detach and --quiet flags when using --rollback
Commit 78c204ef79 added
(f9bd8ec8b268581f93095c5a80679f0a8ff498bf in the moby repo)
a validation to prevent `--rollback` from being used
in combination with other flags that update the
service spec.

This validation was not taking into account that
some flags only affect the CLI behavior, and
are okay to be used when rolling back.

This patch updates the validation, and adds
`--quiet` and `--detach` to the list of allowed
flags.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit f10f29df8d)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 17:31:40 -07:00
7950a39242 Update deprecated.md for removal of --email flag
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 43239f62bedc4721d27744d21c122622988bb3ae)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 17:30:43 -07:00
5bd868da90 Fix cancelling builder on chunked requests
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit 23628bd7efb0aae565cea5ee040e373b0c93fd8d)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 17:29:44 -07:00
e142124317 Do not reuse a http.Request after a failure in callWithRetry
Closes: #33412

Signed-off-by: Felix Abecassis <fabecassis@nvidia.com>
(cherry picked from commit 62871ef2fa52b0a2e426c30f36d35a9ba1e92fac)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 17:28:34 -07:00
0fa9fe7713 Fixing e2e test
Because of cherry-pick from commit
5efcec77170dd6c4087d4f1fb3695c32d94edfb4 into
components/cli/vendor/github.com/docker/docker

Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
2017-06-10 00:27:43 +00:00
69fc734572 Comment out vendoring for docker/docker in components/cli
Because of cherry-pick from commit 5efcec77170dd6c4087d4f1fb3695c32d94edfb4
into components/cli/vendor/github.com/docker/docker

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-10 00:27:43 +00:00
5e405a94e8 Ensure service images get default tag and print familiar strings
Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
(cherry picked from commit 5efcec77170dd6c4087d4f1fb3695c32d94edfb4)

Conflicts resolved:
components/cli/vendor/github.com/docker/docker/client/service_create.go

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-10 00:27:05 +00:00
897b692e1c Merge pull request #53 from seemethere/cherry_pick_33419
libcontainerd: fix reaper goroutine position
2017-06-09 17:21:17 -07:00
d8fc4f1ad2 Merge pull request #54 from andrewhsu/opost
revert Set OPOST on bsd
2017-06-09 17:17:37 -07:00
af4ae65a0d Merge pull request #51 from seemethere/cherry_pick_33307
Event tests need to wait for events
2017-06-09 16:59:23 -07:00
c8fa12c15c Merge pull request #49 from seemethere/cherry_pick_33464
Do not log the CA config CA signing key in debug mode.
2017-06-09 15:36:06 -07:00
36f4ffb042 Merge pull request #50 from seemethere/cherry_pick_33524
Fix Cache with ONBUILD
2017-06-09 15:34:01 -07:00
0cbae9b2ee Merge pull request #48 from seemethere/cherry_pick_33330
Don't create source directory while the daemon is being shutdown, fix…
2017-06-09 15:33:19 -07:00
dcd1f685c8 Merge pull request #47 from seemethere/cherry_pick_33249
Add container environment variables correctly to the health check
2017-06-09 15:32:27 -07:00
05648d36b5 Merge pull request #46 from seemethere/cherry_pick_cli_162
Handle case of configs on old daemon
2017-06-09 15:31:43 -07:00
e1ebcf33f6 Merge pull request #45 from seemethere/cherry_pick_33578
Service alias should not be copied to task alias
2017-06-09 15:31:00 -07:00
4ea6f802a5 Update golang 1.8.3
Signed-off-by: Stefan Scherer <scherer_stefan@icloud.com>
(cherry picked from commit b38c49411b)
Signed-off-by: Tibor Vass <tibor@docker.com>
2017-06-09 22:26:59 +00:00
be7a008421 revendoring netlink in engine component
bumping vendor of vishvananda/netlink to bd6d5de5ccef2d66b0a26177928d0d8895d7f969

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-09 21:37:15 +00:00
fcd821892e revendoring libnetwork in engine component
bumping vendor of docker/libnetwork to 411846172fb457a6459ea94eb536f91717ee0a04

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-09 21:27:44 +00:00
76b0c4884b Merge pull request #14 from mlaventure/update-containerd
Update containerd to cfb82a876ecc11b5ca0977d1733adbe58599088a
2017-06-09 14:22:20 -07:00
1f928815e5 Revert "Set OPOST on bsd"
This reverts commit fff42c853a.

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-09 20:55:50 +00:00
ad8c94e585 Revert "[vendor] Set OPOST on bsd"
This reverts commit 972de9a657.

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-09 20:55:49 +00:00
058e676c7f Revert "Comment out vendoring for docker/docker in components/cli"
This reverts commit 8928dccaa6.

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-09 20:55:41 +00:00
11637f7d81 Re-vendor swarmkit to include the following fixes:
- https://github.com/docker/swarmkit/pull/2218
- https://github.com/docker/swarmkit/pull/2215
- https://github.com/docker/swarmkit/pull/2233

Signed-off-by: Ying <ying.li@docker.com>
2017-06-09 13:25:15 -07:00
5965f0216f libcontainerd: fix reaper goroutine position
It has observed defunct containerd processes accumulating over
time while dockerd was permanently failing to restart containerd.
Due to a bug in the runContainerdDaemon() function, dockerd does not clean up
its child process if containerd already exits very soon after the (re)start.

The reproducer and analysis below comes from docker 1.12.x but bug
still applies on latest master.

- from libcontainerd/remote_linux.go:

  329 func (r *remote) runContainerdDaemon() error {
   :
   :      // start the containerd child process
   :
  403     if err := cmd.Start(); err != nil {
  404             return err
  405     }
   :
   :      // If containerd exits very soon after (re)start, it is
possible
   :      // that containerd is already in defunct state at the time
when
   :      // dockerd gets here. The setOOMScore() function tries to
write
   :      // to /proc/PID_OF_CONTAINERD/oom_score_adj. However, this
fails
   :      // with errno EINVAL because containerd is defunct. Please see
   :      // snippets of kernel source code and further explanation
below.
   :
  407     if err := setOOMScore(cmd.Process.Pid, r.oomScore); err != nil
{
  408             utils.KillProcess(cmd.Process.Pid)
   :
   :              // Due to the error from write() we return here. As
the
   :              // goroutine that would clean up the child has not
been
   :              // started yet, containerd remains in the defunct
state
   :              // and never gets reaped.
   :
  409             return err
  410     }
   :
  417     go func() {
  418             cmd.Wait()
  419             close(r.daemonWaitCh)
  420     }() // Reap our child when needed
   :
  423 }

This is the kernel function that gets invoked when dockerd tries to
write
to /proc/PID_OF_CONTAINERD/oom_score_adj.

- from fs/proc/base.c:

 1197 static ssize_t oom_score_adj_write(struct file *file, ...
 1198                                         size_t count, loff_t
*ppos)
 1199 {
   :
 1223         task = get_proc_task(file_inode(file));
   :
   :          // The defunct containerd process does not have a virtual
   :          // address space anymore, i.e. task->mm is NULL. Thus the
   :          // following code returns errno EINVAL to dockerd.
   :
 1230         if (!task->mm) {
 1231                 err = -EINVAL;
 1232                 goto err_task_lock;
 1233         }
   :
 1253 err_task_lock:
   :
 1257         return err < 0 ? err : count;
 1258 }

The purpose of the following program is to demonstrate the behavior of
the oom_score_adj_write() function in connection with a defunct process.

$ cat defunct_test.c

\#include <unistd.h>

main()
{
    pid_t pid = fork();

    if (pid == 0)
        // child
        _exit(0);

    // parent
    pause();
}

$ make defunct_test
cc     defunct_test.c   -o defunct_test

$ ./defunct_test &
[1] 3142

$ ps -f | grep defunct_test | grep -v grep
root      3142  2956  0 13:04 pts/0    00:00:00 ./defunct_test
root      3143  3142  0 13:04 pts/0    00:00:00 [defunct_test] <defunct>

$ echo "ps 3143" | crash -s
  PID    PPID  CPU       TASK        ST  %MEM     VSZ    RSS  COMM
  3143   3142   2  ffff880035def300  ZO   0.0       0      0
defunct_test

$ echo "px ((struct task_struct *)0xffff880035def300)->mm" | crash -s
$1 = (struct mm_struct *) 0x0
                          ^^^ task->mm is NULL

$ cat /proc/3143/oom_score_adj
0

$ echo 0 > /proc/3143/oom_score_adj
-bash: echo: write error: Invalid argument"

---

This patch fixes the above issue by making sure we start the reaper
goroutine as soon as possible.

Signed-off-by: Antonio Murdaca <runcom@redhat.com>

(cherry picked from commit 27087eacbf96e6ef9d48a6d3dc89c7c1cff155b4)

Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>

Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 13:23:20 -07:00
3910d5b571 Check signal is unset before using user stopsignal
This fixes an issue where if a stop signal is set, and a user sends
SIGKILL, `container.ExitOnNext()` is not set, thus causing the container
to restart.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
(cherry picked from commit 114652ab86609e5c0cbfad84f642942b466a0596)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 13:21:29 -07:00
03d8258b7d Event tests need to wait for events
Signed-off-by: Dong Chen <dongluo.chen@docker.com>
(cherry picked from commit 59b2d0473af5155bb31adcc17bc36857ebd13a15)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 13:19:58 -07:00
1c2ce3d977 Fix Cache with ONBUILD
Signed-off-by: Daniel Nephin <dnephin@docker.com>
(cherry picked from commit f1ade82d82e6436971c6b7d08eb1da57ed9ba756)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 13:17:47 -07:00
eb4ef82087 Do not log the CA config CA signing key in debug mode.
Signed-off-by: Ying Li <ying.li@docker.com>
(cherry picked from commit d60f18204978d438d1eb336512576d47991c8ac1)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 13:16:19 -07:00
d09575fb8f Don't create source directory while the daemon is being shutdown, fix #30348
If a container mount the socket the daemon is listening on into
container while the daemon is being shutdown, the socket will
not exist on the host, then daemon will assume it's a directory
and create it on the host, this will cause the daemon can't start
next time.

fix issue https://github.com/moby/moby/issues/30348

To reproduce this issue, you can add following code

```
--- a/daemon/oci_linux.go
+++ b/daemon/oci_linux.go
@@ -8,6 +8,7 @@ import (
        "sort"
        "strconv"
        "strings"
+       "time"

        "github.com/Sirupsen/logrus"
        "github.com/docker/docker/container"
@@ -666,7 +667,8 @@ func (daemon *Daemon) createSpec(c *container.Container) (*libcontainerd.Spec, e
        if err := daemon.setupIpcDirs(c); err != nil {
                return nil, err
        }
-
+       fmt.Printf("===please stop the daemon===\n")
+       time.Sleep(time.Second * 2)
        ms, err := daemon.setupMounts(c)
        if err != nil {
                return nil, err

```

step1 run a container which has `--restart always` and `-v /var/run/docker.sock:/sock`
```
$ docker run -ti --restart always -v /var/run/docker.sock:/sock busybox
/ #

```
step2 exit the the container
```
/ # exit
```
and kill the daemon when you see
```
===please stop the daemon===
```
in the daemon log

The daemon can't restart again and fail with `can't create unix socket /var/run/docker.sock: is a directory`.

Signed-off-by: Lei Jitang <leijitang@huawei.com>

(cherry picked from commit 7318eba5b2f8bb4b867ca943c3229260ca98a3bc)

Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>

Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 13:13:40 -07:00
13934b618c Add container environment variables correctly to the health check
The health check process doesn't have all the environment
varialbes in the container or has them set incorrectly.

This patch should fix that problem.

Signed-off-by: Boaz Shuster <ripcurld.github@gmail.com>
(cherry picked from commit 5836d86ac4d617e837d94010aa60384648ab59ea)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 13:09:40 -07:00
e9b6305e1d Handle case of configs on old daemon
If configs are declared for a service and pointing on an old
daemon, error out properly (instead of "page not found").

If there is no configs declared, don't call convertServiceConfigObjs
to avoid having an error.

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
(cherry picked from commit cf5550c426)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 13:06:34 -07:00
94f4d72c55 Service alias should not be copied to task alias
If a service alias is copied to task, then the DNS resolution on the
service name will resolve to service VIP and all of Task-IPs and that
will break the concept of vip based load-balancing resulting in all the
dns-rr caching issues.

This is a regression introduced in #33130

Signed-off-by: Madhu Venugopal <madhu@docker.com>
(cherry picked from commit 38c15531501578b96d34be5ce7f33a0be6be078f)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-06-09 12:55:46 -07:00
765e46f7cc Update containerd to cfb82a876ecc11b5ca0977d1733adbe58599088a
Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
2017-06-09 09:33:50 -04:00
f15c4fd160 Fixing stack deploy tests to not contact registry
Because of cherry-pick from commit
f790e839fc

Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
2017-06-08 15:20:09 -07:00
66d168dd55 Change --no-resolve-image flag to --resolve-image string flag
Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
(cherry picked from commit f790e839fc)
Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
2017-06-08 14:25:50 -07:00
59d6ed0a4d Enable client side digest pinning for stack deploy
Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
(cherry picked from commit 9f1bea2657)
Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
2017-06-08 14:24:57 -07:00
402dd4a9ea Merge pull request #38 from andrewhsu/rc2
bump VERSION files to 17.06.0-ce-rc2
2017-06-06 16:05:18 -07:00
14ff8286f7 Remove unneeded line in 17.06 CHANGELOG
Also update release date

Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
2017-06-06 15:58:28 -07:00
eb1565bba1 Merge pull request #40 from mlaventure/17.06-rc2-changelog
Update 17.06 CHANGELOG with rc2 changes
2017-06-06 15:19:15 -07:00
de513d091d Merge pull request #41 from andrewhsu/update-pack
update with latest changes from docker-ce-packaging
2017-06-06 15:16:36 -07:00
1d4b51cccb Update 17.06 CHANGELOG with rc2 changes
Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
2017-06-06 14:50:01 -07:00
b5411da4de Make the hash_files target non interactive
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
(cherry picked from commit 097516fc76a2c9acf798dc523326904520ff1fc7)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 21:26:55 +00:00
7239874782 Add file hashing for static files
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
(cherry picked from commit a1b7f6f3407546ff41ed2e0d16d7e1b2a8ac0ef4)

Conflicts:
components/packaging/static/Makefile
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 21:26:38 +00:00
18355074d7 Move the hashing of files to its own target
So we can use it at will

Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
(cherry picked from commit 533a843393bd7c3674074ec9af73c8e666fc7484)

Conflicts:
components/packaging/static/Makefile
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 21:24:53 +00:00
1f94b6c7ec set $GOPATH/bin for s390x
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit f6d30c3631abee04aa3586cba36960901f705fe2)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 21:21:03 +00:00
737e92d753 no need for tty
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit ade95a2b6f8a16aa7e6c387dc9c9dd48bba9a672)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 21:20:47 +00:00
97b6afddfe bump VERSION files to 17.06.0-ce-rc2
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 16:54:24 +00:00
00327e2c1c Merge pull request #37 from andrewhsu/gen-man
generate man pages
2017-06-06 02:16:57 -07:00
0dbee88694 generate man pages
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit e14fca86f8230e10d79a7bfae2873a98a16bb44d)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 09:01:36 +00:00
ce44edfbf8 Merge pull request #19 from aluzzardi/revendor-swarmkit
[17.06] Re-vendor SwarmKit to 4b872cfac8ffc0cc7fff434902cc05dbc7612da5
2017-06-06 00:37:05 -07:00
0efdcc403e Re-vendor SwarmKit to 4b872cfac8ffc0cc7fff434902cc05dbc7612da5
Includes:
- docker/swarmkit#2203
- docker/swarmkit#2210
- docker/swarmkit#2212

Signed-off-by: Andrea Luzzardi <aluzzardi@gmail.com>
Signed-off-by: Tibor Vass <tibor@docker.com>
2017-06-06 05:55:35 +00:00
8cf591e57d Merge pull request #21 from mavenugo/17.06
Vendoring libnetwork b54433f6459b84eae77892d0d359c1bafb133529
2017-06-05 22:25:27 -07:00
5cfdad44b3 Merge pull request #36 from tiborvass/fix-manpages-script
[cli] fix manpages script
2017-06-05 22:19:24 -07:00
5bb86daf51 [cli] fix manpages script
Signed-off-by: Tibor Vass <tibor@docker.com>
2017-06-06 05:05:14 +00:00
9b8ffe709d Merge pull request #25 from mlaventure/chp-moby-33502
Set OPOST on bsd
2017-06-05 21:56:30 -07:00
8928dccaa6 Comment out vendoring for docker/docker in components/cli
Because of cherry-pick from commit 17ec46a24316f59c808c112e3ca46d7c442a785a
into components/cli/vendor/github.com/docker/docker

Signed-off-by: Tibor Vass <tibor@docker.com>
2017-06-06 03:45:20 +00:00
972de9a657 [vendor] Set OPOST on bsd
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
(cherry picked from commit 17ec46a24316f59c808c112e3ca46d7c442a785a)

Manually update pkg/term vendoring to match changes in moby

Signed-off-by: Tibor Vass <tibor@docker.com>
2017-06-06 03:42:16 +00:00
9dad38d70a Merge pull request #26 from mlaventure/chp-moby-33500
Fix ONBUILD COPY
2017-06-05 20:24:21 -07:00
699fcc1433 Vendoring libnetwork
This is a 17.06 equivalent cherry-pick of
https://github.com/moby/moby/pull/33463

Signed-off-by: Madhu Venugopal <madhu@docker.com>
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 03:18:59 +00:00
40d95168e1 Merge pull request #16 from mavenugo/cli-1706-stack
Host and Bridge network support in docker stack deploy
2017-06-05 19:22:40 -07:00
d236b522c5 Merge pull request #33 from andrewhsu/pkg-update
update with latest from docker-ce-packaging
2017-06-05 18:40:24 -07:00
111c39aff1 Change ARCH to be reflective
Signed-off-by: Eli Uriegas <seemethere101@gmail.com>
(cherry picked from commit 930db40ec8424555612ce70781e94d5cb172aa4c)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 00:02:26 +00:00
c1c839c28b Rename deb dockerfiles to $(uname -m) equivalents
Signed-off-by: Eli Uriegas <seemethere101@gmail.com>
(cherry picked from commit 3f7e423438733fb5f593f666d12714ae2f63fcc9)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 00:02:26 +00:00
f650eb38a6 Update GO_VERSION to 1.8.3 (#8)
Signed-off-by: Eli Uriegas <seemethere101@gmail.com>(cherry picked from commit 121e1c478d94a9be5713e944dde113f8637d6e41)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 00:02:26 +00:00
aa3a4c2846 Add armhf dockerfiles for deb building (#4)
Add armhf dockerfiles for deb building

Signed-off-by: Eli Uriegas <seemethere101@gmail.com>
(cherry picked from commit f0c8cea1b79b049743cd1503f7ac4a34c265f476)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 00:02:26 +00:00
6a52ceebf4 Add DISTRO/SUITE env. variables to the RPM dockerfiles
Signed-off-by: Roberto Gandolfo Hashioka <roberto_hashioka@hotmail.com>
(cherry picked from commit c4a9e41495fac5e4aa72151b80200aa453f65e08)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-06-06 00:02:26 +00:00
c6dd1afaa6 Merge pull request #24 from tiborvass/man-yaml-scripts
Add scripts and targets for manpages and yamldocs
2017-06-05 16:06:46 -07:00
f148100b07 Add scripts and targets for manpages and yamldocs
Signed-off-by: Tibor Vass <tibor@docker.com>
(cherry picked from commit ff615dbc4d)
2017-06-05 22:51:19 +00:00
8ce2c283f7 Merge pull request #23 from tiborvass/import-man-docs-completion
Import man docs completion
2017-06-05 15:40:42 -07:00
b2dacf38db Merge pull request #8 from mlaventure/chp-moby-33341
Do not clear swarm directory at the begining of swarm init and swarm …
2017-06-05 09:57:27 -07:00
6c636490a3 Merge pull request #22 from mlaventure/chp-cli-136
Fix stack compose bind-mount volumes for Windows
2017-06-05 09:54:06 -07:00
c1f7e41e4e Merge pull request #12 from mlaventure/chp-cli-135
Include stack service configs in service specs
2017-06-05 09:46:58 -07:00
a148bf60af Merge pull request #11 from mlaventure/chp-cli-134
Remove stack configs on stack removal
2017-06-05 09:46:13 -07:00
4910da9ddb Merge pull request #10 from mlaventure/chp-cli-126
Handle a Docker daemon without registry info
2017-06-05 09:45:31 -07:00
9e9c4b52b9 Fix ONBUILD COPY
the source was missing from the second dispatch

Signed-off-by: Daniel Nephin <dnephin@docker.com>
(cherry picked from commit 3f2604157790408acf5ad05c74cebe105f2b6979)
2017-06-05 08:01:05 -07:00
fff42c853a Set OPOST on bsd
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
(cherry picked from commit 17ec46a24316f59c808c112e3ca46d7c442a785a)
2017-06-05 07:45:38 -07:00
2bdf7010a1 Merge pull request #7 from mlaventure/chp-moby-33308
remove RuntimeData from cluster and types
2017-06-05 06:10:46 -07:00
46f679cc1d Merge pull request #5 from mlaventure/chp-moby-33311
Adding support for DNS search on RS1
2017-06-05 06:09:52 -07:00
3a237eae5f Merge pull request #4 from mlaventure/chp-moby-33376
Ensure that a device mapper task is referenced until task is complete
2017-06-05 06:09:13 -07:00
8330e07f4a Merge pull request #3 from mlaventure/chp-moby-33365
Update go-winio to v0.4.2
2017-06-05 06:05:37 -07:00
9a732dbff3 Merge pull request #2 from mlaventure/chp-moby-33422
Don't unmount entire plugin manager tree on remove
2017-06-05 05:37:07 -07:00
08cb13b09b Fix stack compose bind-mount volumes for Windows
For stack compose files, use filepath.IsAbs instead of path.IsAbs, for
bind-mounted service volumes, because filepath.IsAbs handles Windows
paths, while path.IsAbs does not.

Signed-off-by: John Stephens <johnstep@docker.com>
(cherry picked from commit 9043d39dea)
2017-06-02 12:01:27 -07:00
09185931e1 Merge pull request #9 from mlaventure/chp-moby-33387
Bump go to go1.8.3
2017-06-02 10:38:59 -07:00
8d44c9a703 Merge pull request #1 from mlaventure/chp-moby-33409
prevent image prune panic
2017-06-02 10:38:26 -07:00
0334cfffff Merge pull request #20 from mlaventure/chp-moby-33474
Use actual cli version for TestConfigHTTPHeader
2017-06-02 10:33:44 -07:00
a9548340cb Use actual cli version for TestConfigHTTPHeader
Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
(cherry picked from commit 0b90edc22fa95e04b4d3208a72bb276be699b260)
2017-06-01 17:13:23 -07:00
91d25d6fe0 Only set default aliases when the network is user defined.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
(cherry picked from commit d5b505ee8c)
2017-05-31 19:07:11 -07:00
6c534bb5a0 Add tests for verifyExternalNetwork
Signed-off-by: Daniel Nephin <dnephin@docker.com>
(cherry picked from commit 341703d21e)
2017-05-31 19:07:08 -07:00
1c46b8f4d3 With the introduction of node-local network support, docker services can
be attached to special networks such as host and bridge. This fix brings
in the required changes to make sure the stack file accepts these
networks as well.

Signed-off-by: Madhu Venugopal <madhu@docker.com>
(cherry picked from commit 123f0bfd98)
2017-05-31 19:07:05 -07:00
f156aa746e Adding unit tests for pin by digest (client)
Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
(cherry picked from commit 75c7536d2e2e328b644bf69153de879d1d197988)
2017-05-31 11:40:48 -07:00
5c5cc1fccd Ensure service images get default tag and print familiar strings
Signed-off-by: Nishant Totla <nishanttotla@gmail.com>
(cherry picked from commit 5efcec77170dd6c4087d4f1fb3695c32d94edfb4)
2017-05-31 11:40:48 -07:00
cd6b682d4f Include stack service configs in service specs
Signed-off-by: John Stephens <johnstep@docker.com>
(cherry picked from commit b7cac96f69)
2017-05-31 11:37:32 -07:00
19e7c1efe9 Remove stack configs on stack removal
Signed-off-by: John Stephens <johnstep@docker.com>
(cherry picked from commit f05cd11ee2)
2017-05-31 11:35:59 -07:00
32807d0a87 Handle a Docker daemon without registry info
The current implementation of the ElectAuthServer doesn't handle well when the
default Registry server is not included in the response from the daemon Info
endpoint.

That leads to the storage and usage of the credentials for the default registry
(`https://index.docker.io/v1/`) under an empty string on the client config file.

Sample config file after a login via a Docker Daemon without Registry
information:
```json
{
	"auths": {
		"": {
			"auth": "***"
		}
	}
}
```

That can lead to duplication of the password for the default registry and
authentication failures against the default registry if a pull/push is performed
without first authenticating via the misbehaving daemon.

Also, changes the output of the warning message from stdout to sdterr as
per dnephin suggestion.

Signed-off-by: Marcus Martins <marcus@docker.com>
(cherry picked from commit 862649707e)
2017-05-31 11:34:01 -07:00
e7776047c2 Bump go to go1.8.3
Note that go1.8.2 contains a security fix (CVE-2017-8932).

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
(cherry picked from commit 0c7c900e9e66335a6bd486be008af43ae83a5a37)
2017-05-31 11:29:38 -07:00
30e966bc97 Do not clear swarm directory at the begining of swarm init and swarm join now.
However, do clear the directory if init or join fails, because we don't
want to leave it in a half-finished state.

Signed-off-by: Ying Li <ying.li@docker.com>
(cherry picked from commit bf3e9293a66c77a2fddf4e691222898846b4af9f)
2017-05-31 11:25:56 -07:00
bb1fe809d2 remove RuntimeData from cluster and types
Signed-off-by: Evan Hazlett <ejhazlett@gmail.com>
(cherry picked from commit 8eeba751983d6499a07fcd21c26f381eb1d17d19)
2017-05-31 11:08:24 -07:00
e724dcd16b Adding support for DNS search on RS1
Signed-off-by: Sandeep Bansal <sabansal@microsoft.com>
(cherry picked from commit b8e8dcd6e031d005a2d46d95f84da35b436cd870)
2017-05-31 10:55:38 -07:00
96c9cd5c39 Ensure that a device mapper task is referenced until task is complete
DeviceMapper tasks in go use SetFinalizer to clean up C construct
counterparts in the C LVM library.  While thats well and good, it relies
heavily on the exact interpretation of when the golang garbage collector
determines that an object is unreachable is subject to reclaimation.
While common sense would assert that for stack variables (which these DM
tasks always are), are unreachable when the stack frame in which they
are declared returns, thats not the case.  According to this:

https://golang.org/pkg/runtime/#SetFinalizer

The garbage collector decides that, if a function calls into a
systemcall (which task.run() always will in LVM), and there are no
subsequent references to the task variable within that stack frame, then
it can be reclaimed.  Those conditions are met in several devmapper.go
routines, and if the garbage collector runs in the middle of a
deviceMapper operation, then the task can be destroyed while the
operation is in progress, leading to crashes, failed operations and
other unpredictable behavior.

The fix is to use the KeepAlive interface:

https://golang.org/pkg/runtime/#KeepAlive

The KeepAlive method is effectively an empy reference that fools the
garbage collector into thinking that a variable is still reachable.  By
adding a call to KeepAlive in the task.run() method, we can ensure that
the garbage collector won't reclaim a task object until its execution
within the deviceMapper C library is complete.

Signed-off-by: Neil Horman <nhorman@tuxdriver.com>
(cherry picked from commit d764d8b16624e4924b3949273089f851efa0f717)
2017-05-31 10:42:53 -07:00
4360062d33 Update go-winio to v0.4.2
Signed-off-by: Darren Stahl <darst@microsoft.com>
(cherry picked from commit 3f13107223a9753d709c196c46f1c846041fc0c3)
2017-05-31 10:39:35 -07:00
5acb14a901 Don't unmount entire plugin manager tree on remove
This was mistakenly unmounting everything under `plugins/*` instead of
just `plugins/<id>/*` anytime a plugin is removed.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
(cherry picked from commit db5f31732a9868c1e9e4f9a49be70b794ff82d4f)
2017-05-31 10:37:33 -07:00
43484d9284 prevent image prune panic
Signed-off-by: Alfred Landrum <alfred.landrum@docker.com>
(cherry picked from commit 32da2a4234c5c68ff466dc1afc91ba98dbbe199a)
2017-05-31 09:58:11 -07:00
7f8486a39a pass in git commit override when building deb
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit 3c70b56937c415c6bf3516aeaa5231c6d7d3145d)
2017-05-25 06:00:03 +00:00
5f615dd295 override ver and git commit when building all deb pkgs
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit 4397990e00863254d78c44fbe904a70272b5b340)
2017-05-25 05:59:47 +00:00
f3810787c8 pass in optional GITCOMMIT override
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit 446af3a9b7)
2017-05-24 23:03:09 +00:00
2bcfe6ffc2 bump VERSION files to 17.06.0-ce-rc1
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-05-24 02:24:43 +00:00
0b4548c769 allow version number to be set in builds
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
(cherry picked from commit 3dfe334a7a)
2017-05-24 02:21:53 +00:00
1934 changed files with 108356 additions and 94211 deletions

View File

@ -5,69 +5,7 @@ information on the list of deprecated flags and APIs please have a look at
https://docs.docker.com/engine/deprecated/ where target removal dates can also
be found.
## 17.07.0-ce (2017-08-XX)
### API & Client
* Add support for proxy configuration in config.json [docker/cli#93](https://github.com/docker/cli/pull/93)
* Enable pprof/debug endpoints by default [moby/moby#32453](https://github.com/moby/moby/pull/32453)
* Passwords can now be passed using `STDIN` using the new `--password-stdin` flag on `docker login` [docker/cli#271](https://github.com/docker/cli/pull/271)
+ Add `--detach` to docker scale [docker/cli#243](https://github.com/docker/cli/pull/243)
* Prevent `docker logs --no-stream` from hanging due to non-existing containers [moby/moby#34004](https://github.com/moby/moby/pull/34004)
- Fix `docker stack ps` printing error to `stdout` instead of `stderr` [docker/cli#298](https://github.com/docker/cli/pull/298)
* Fix progress bar being stuck on `docker service create` if an error occurs during deploy [docker/cli#259](https://github.com/docker/cli/pull/259)
* Improve presentation of progress bars in interactive mode [docker/cli#260](https://github.com/docker/cli/pull/260) [docker/cli#237](https://github.com/docker/cli/pull/237)
* Print a warning if `docker login --password` is used, and recommend `--password-stdin` [docker/cli#270](https://github.com/docker/cli/pull/270)
* Make API version negotiation more robust [moby/moby#33827](https://github.com/moby/moby/pull/33827)
* Hide `--detach` when connected to daemons older than Docker 17.05 [docker/cli#219](https://github.com/docker/cli/pull/219)
+ Add `scope` filter in `GET /networks/(id or name)` [moby/moby#33630](https://github.com/moby/moby/pull/33630)
### Builder
* Implement long running interactive session and sending build context incrementally [moby/moby#32677](https://github.com/moby/moby/pull/32677) [docker/cli#231](https://github.com/docker/cli/pull/231) [moby/moby#33859](https://github.com/moby/moby/pull/33859)
* Warn on empty continuation lines [moby/moby#33719](https://github.com/moby/moby/pull/33719)
- Fix `.dockerignore` entries with a leading `/` not matching anything [moby/moby#32088](https://github.com/moby/moby/pull/32088)
### Logging
- Fix wrong filemode for rotate log files [moby/moby#33926](https://github.com/moby/moby/pull/33926)
- Fix stderr logging for journald and syslog [moby/moby#33832](https://github.com/moby/moby/pull/33832)
### Runtime
* Allow stopping of paused container [moby/moby#34027](https://github.com/moby/moby/pull/34027)
+ Add quota support for the overlay2 storage driver [moby/moby#32977](https://github.com/moby/moby/pull/32977)
* Remove container locks on `docker ps` [moby/moby#31273](https://github.com/moby/moby/pull/31273)
* Store container names in memdb [moby/moby#33886](https://github.com/moby/moby/pull/33886)
* Fix race condition between `docker exec` and `docker pause` [moby/moby#32881](https://github.com/moby/moby/pull/32881)
* Devicemapper: Rework logging and add `--storage-opt dm.libdm_log_level` [moby/moby#33845](https://github.com/moby/moby/pull/33845)
* Devicemapper: Prevent "device in use" errors if deferred removal is enabled, but not deferred deletion [moby/moby#33877](https://github.com/moby/moby/pull/33877)
* Devicemapper: Use KeepAlive to prevent tasks being garbage-collected while still in use [moby/moby#33376](https://github.com/moby/moby/pull/33376)
* Report inetermediate prune results if prune is cancelled [moby/moby#33979](https://github.com/moby/moby/pull/33979)
- Fix run `docker rename <container-id> new_name` concurrently resulting in the having multiple names [moby/moby#33940](https://github.com/moby/moby/pull/33940)
* Fix file-descriptor leak and error handling [moby/moby#33713](https://github.com/moby/moby/pull/33713)
- Fix SIGSEGV when running containers [docker/cli#303](https://github.com/docker/cli/pull/303)
* Prevent a goroutine leak when healthcheck gets stopped [moby/moby#33781](https://github.com/moby/moby/pull/33781)
* Image: Improve store locking [moby/moby#33755](https://github.com/moby/moby/pull/33755)
* Fix Btrfs quota groups not being removed when container is destroyed [moby/moby#29427](https://github.com/moby/moby/pull/29427)
* Libcontainerd: fix defunct containerd processes not being properly reaped [moby/moby#33419](https://github.com/moby/moby/pull/33419)
* Preparations for Linux Containers on Windows
* LCOW: Dedicated scratch space for service VM utilities [moby/moby#33809](https://github.com/moby/moby/pull/33809)
* LCOW: Support most operations excluding remote filesystem [moby/moby#33241](https://github.com/moby/moby/pull/33241) [moby/moby#33826](https://github.com/moby/moby/pull/33826)
* LCOW: Change directory from lcow to "Linux Containers" [moby/moby#33835](https://github.com/moby/moby/pull/33835)
* LCOW: pass command arguments without extra quoting [moby/moby#33815](https://github.com/moby/moby/pull/33815)
* LCOW: Updates necessary due to platform schema change [moby/moby#33785](https://github.com/moby/moby/pull/33785)
### Swarm Mode
* Initial support for plugable secret backends [moby/moby#34157](https://github.com/moby/moby/pull/34157) [moby/moby#34123](https://github.com/moby/moby/pull/34123)
* Sort swarm stacks and nodes using natural sorting [docker/cli#315](https://github.com/docker/cli/pull/315)
* Make engine support cluster config event [moby/moby#34032](https://github.com/moby/moby/pull/34032)
* Only pass a join address when in the process of joining a cluster [moby/moby#33361](https://github.com/moby/moby/pull/33361)
* Fix error during service creation if a network with the same name exists both as "local" and "swarm" scoped network [docker/cli#184](https://github.com/docker/cli/pull/184)
* (experimental) Add support for plugins on swarm [moby/moby#33575](https://github.com/moby/moby/pull/33575)
## 17.06.0-ce (2017-06-07)
## 17.06.0-ce (2017-06-19)
### Builder
@ -87,6 +25,10 @@ be found.
+ Add new `ca ` subcommand to `docker swarm` to allow managing a swarm CA [#docker/cli/48](https://github.com/docker/cli/pull/48)
+ Add credential-spec to compose [#docker/cli/71](https://github.com/docker/cli/pull/71)
+ Add support for csv format options to `--network` and `--network-add` [#docker/cli/62](https://github.com/docker/cli/pull/62) [#33130](https://github.com/moby/moby/pull/33130)
- Fix stack compose bind-mount volumes on Windows [#docker/cli/136](https://github.com/docker/cli/pull/136)
- Correctly handle a Docker daemon without registry info [#docker/cli/126](https://github.com/docker/cli/pull/126)
+ Allow --detach and --quiet flags when using --rollback [#docker/cli/144](https://github.com/docker/cli/pull/144)
+ Remove deprecated `--email` flag from `docker login` [#docker/cli/143](https://github.com/docker/cli/pull/143)
### Distribution
@ -121,6 +63,14 @@ be found.
+ Add daemon option to allow pushing foreign layers [#33151](https://github.com/moby/moby/pull/33151)
- Fix an issue preventing containerd to be restarted after it died [#32986](https://github.com/moby/moby/pull/32986)
+ Add cluster events to Docker event stream. [#32421](https://github.com/moby/moby/pull/32421)
+ Add support for DNS search on windows [#33311](https://github.com/moby/moby/pull/33311)
* Upgrade to Go 1.8.3 [#33387](https://github.com/moby/moby/pull/33387)
- Prevent a containerd crash when journald is restarted [#containerd/930](https://github.com/containerd/containerd/pull/930)
- Fix healthcheck failures due to invalid environment variables [#33249](https://github.com/moby/moby/pull/33249)
- Prevent a directory to be created in lieu of the daemon socket when a container mounting it is to be restarted during a shutdown [#30348](https://github.com/moby/moby/pull/33330)
- Prevent a container to be restarted upon stop if its stop signal is set to `SIGKILL` [#33335](https://github.com/moby/moby/pull/33335)
- Ensure log drivers get passed the same filename to both StartLogging and StopLogging endpoints [#33583](https://github.com/moby/moby/pull/33583)
- Remove daemon data structure dump on `SIGUSR1` to avoid a panic [#33598](https://github.com/moby/moby/pull/33598)
### Security
@ -136,6 +86,9 @@ be found.
+ Add API to rotate swarm CA certificate [#32993](https://github.com/moby/moby/pull/32993)
* Service digest pining is now handled client side [#32388](https://github.com/moby/moby/pull/32388), [#33239](https://github.com/moby/moby/pull/33239)
+ Placement now also take platform in account [#33144](https://github.com/moby/moby/pull/33144)
- Fix possible hang when joining fails [#docker-ce/19](https://github.com/docker/docker-ce/pull/19)
- Fix an issue preventing external CA to be accepted [#33341](https://github.com/moby/moby/pull/33341)
- Fix possible orchestration panic in mixed version clusters [#swarmkit/2233](https://github.com/docker/swarmkit/pull/2233)
## 17.05.0-ce (2017-05-04)

View File

@ -4,4 +4,3 @@ Want to contribute on Docker CE? Awesome!
* Changes to the `engine` should be directed upstream to https://github.com/moby/moby
* Changes to the `cli` should be directed upstream to https://github.com/docker/cli
* Changes to the `packaging` should be directed upstream to https://github.com/docker/docker-ce-packaging

View File

@ -25,6 +25,3 @@ clean: ## clean the build artifacts
-$(MAKE) -C $(CLI_DIR) clean
-$(MAKE) -C $(ENGINE_DIR) clean
-$(MAKE) -C $(PACKAGING_DIR) clean
vendor: $(CLI_DIR)/build/docker
docker run --rm -it -v $(CLI_DIR):/go/src/github.com/docker/cli -v $(ENGINE_DIR):/go/src/github.com/docker/docker docker-cli-dev sh -c 'cd /go/src/github.com/docker/docker && git init && git add . && git -c user.name=user -c user.email=email@example.com commit -m first && cd /go/src/github.com/docker/cli && vndr; rm -rf /go/src/github.com/docker/docker/.git'

View File

@ -1,24 +1,12 @@
# Docker CE
This repository hosts open source components of Docker CE products. The
`master` branch serves to unify the upstream components on a regular
basis. Long-lived release branches host the code that goes into a product
version for the lifetime of the product.
This repository hosts open source components of Docker Community Edition
(CE) products. The `master` branch serves to unify the upstream components
on a regular basis. Long-lived release branches host the code that goes
into a product version for the lifetime of the product.
This repository is solely maintained by Docker, Inc.
## Issues
There are separate issue-tracking repos for the end user Docker CE
products specialized for a platform. Find your issue or file a new issue
for the platform you are using:
* https://github.com/docker/for-linux
* https://github.com/docker/for-mac
* https://github.com/docker/for-win
* https://github.com/docker/for-aws
* https://github.com/docker/for-azure
## Unifying upstream sources
The `master` branch is a combination of components adapted from

View File

@ -1 +1 @@
17.07.0-ce-rc3
17.06.0-ce-rc4

View File

@ -1,8 +0,0 @@
# 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

View File

@ -6,5 +6,3 @@ cli/winresources/rsrc_amd64.syso
/man/man5/
/man/man8/
/docs/yaml/gen/
coverage.txt
profile.out

View File

@ -1,66 +1,59 @@
#
# github.com/docker/cli
#
all: binary
# remove build artifacts
.PHONY: clean
clean:
rm -rf ./build/* cli/winresources/rsrc_* ./man/man[1-9] docs/yaml/gen
@rm -rf ./build/* cli/winresources/rsrc_* ./man/man[1-9] docs/yaml/gen
# run go test
# the "-tags daemon" part is temporary
.PHONY: test
test:
./scripts/test/unit $(shell go list ./... | grep -v '/vendor/')
.PHONY: test-coverage
test-coverage:
./scripts/test/unit-with-coverage $(shell go list ./... | grep -v '/vendor/')
@go test -tags daemon -v $(shell go list ./... | grep -v '/vendor/')
.PHONY: lint
lint:
gometalinter --config gometalinter.json ./...
@gometalinter --config gometalinter.json ./...
.PHONY: binary
binary:
@echo "WARNING: binary creates a Linux executable. Use cross for macOS or Windows."
./scripts/build/binary
@./scripts/build/binary
# build the CLI for multiple architectures
.PHONY: cross
cross:
./scripts/build/cross
@./scripts/build/cross
.PHONY: dynbinary
dynbinary:
./scripts/build/dynbinary
@./scripts/build/dynbinary
.PHONY: watch
watch:
./scripts/test/watch
@./scripts/test/watch
# Check vendor matches vendor.conf
# download dependencies (vendor/) listed in vendor.conf
.PHONY: vendor
vendor: vendor.conf
vndr 2> /dev/null
scripts/validate/check-git-diff vendor
@vndr 2> /dev/null
@scripts/validate/check-git-diff vendor
## generate man pages from go source and markdown
.PHONY: manpages
manpages:
scripts/docs/generate-man.sh
@scripts/docs/generate-man.sh
## generate documentation YAML files consumed by docs repo
.PHONY: yamldocs
yamldocs:
scripts/docs/generate-yaml.sh
## Shellcheck validation
.PHONY: shellcheck
shellcheck:
scripts/validate/shellcheck
@scripts/docs/generate-yaml.sh
cli/compose/schema/bindata.go: cli/compose/schema/data/*.json
go generate github.com/docker/cli/cli/compose/schema
compose-jsonschema: cli/compose/schema/bindata.go
scripts/validate/check-git-diff cli/compose/schema/bindata.go
@scripts/validate/check-git-diff cli/compose/schema/bindata.go

View File

@ -1 +1 @@
17.07.0-ce-rc3
17.06.0-ce-rc4

View File

@ -1,115 +1,49 @@
version: 2
jobs:
lint:
build:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
docker:
- image: docker:17.05
parallelism: 4
steps:
- checkout
- setup_remote_docker:
reusable: true
exclusive: false
- run:
command: docker version
- run:
name: "Lint"
command: |
dockerfile=dockerfiles/Dockerfile.lint
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-linter:$CIRCLE_BUILD_NUM .
docker run --rm cli-linter:$CIRCLE_BUILD_NUM
cross:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
parallelism: 3
steps:
- checkout
- setup_remote_docker:
reusable: true
exclusive: false
- run:
name: "Cross"
command: |
dockerfile=dockerfiles/Dockerfile.cross
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder:$CIRCLE_BUILD_NUM .
name=cross-$CIRCLE_BUILD_NUM-$CIRCLE_NODE_INDEX
docker run \
-e CROSS_GROUP=$CIRCLE_NODE_INDEX \
--name $name cli-builder:$CIRCLE_BUILD_NUM \
make cross
docker cp \
$name:/go/src/github.com/docker/cli/build \
/work/build
- store_artifacts:
path: /work/build
test:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
steps:
- checkout
- setup_remote_docker:
reusable: true
exclusive: false
- run:
name: "Unit Test with Coverage"
command: |
dockerfile=dockerfiles/Dockerfile.dev
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder:$CIRCLE_BUILD_NUM .
docker run --name \
test-$CIRCLE_BUILD_NUM cli-builder:$CIRCLE_BUILD_NUM \
make test-coverage
- run:
name: "Upload to Codecov"
command: |
docker cp \
test-$CIRCLE_BUILD_NUM:/go/src/github.com/docker/cli/coverage.txt \
coverage.txt
apk add -U bash curl
curl -s https://codecov.io/bash | bash
validate:
working_directory: /work
docker: [{image: 'docker:17.06-git'}]
steps:
- checkout
- setup_remote_docker:
reusable: true
exclusive: false
- run:
name: "Validate Vendor, Docs, and Code Generation"
command: |
dockerfile=dockerfiles/Dockerfile.dev
echo "COPY . ." >> $dockerfile
rm -f .dockerignore # include .git
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:
name: "Install Git and SSH"
command: apk add -U git openssh
- checkout
- setup_remote_docker
- run:
name: "Run shellcheck"
name: "Lint"
command: |
dockerfile=dockerfiles/Dockerfile.shellcheck
if [ "$CIRCLE_NODE_INDEX" != "0" ]; then exit; fi
dockerfile=dockerfiles/Dockerfile.lint
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:
jobs:
- lint
- cross
- test
- validate
- shellcheck
docker build -f $dockerfile --tag cli-linter .
docker run cli-linter
- run:
name: "Cross"
command: |
if [ "$CIRCLE_NODE_INDEX" != "1" ]; then exit; fi
dockerfile=dockerfiles/Dockerfile.cross
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder .
docker run --name cross cli-builder make cross
docker cp cross:/go/src/github.com/docker/cli/build /work/build
- run:
name: "Unit Test"
command: |
if [ "$CIRCLE_NODE_INDEX" != "2" ]; then exit; fi
dockerfile=dockerfiles/Dockerfile.dev
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder .
docker run cli-builder make test
- run:
name: "Validate Vendor and Code Generation"
command: |
if [ "$CIRCLE_NODE_INDEX" != "3" ]; then exit; fi
dockerfile=dockerfiles/Dockerfile.dev
echo "COPY . ." >> $dockerfile
docker build -f $dockerfile --tag cli-builder .
docker run cli-builder make -B vendor compose-jsonschema
- store_artifacts:
path: /work/build

View File

@ -1,35 +0,0 @@
package checkpoint
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
checkpointCreateFunc func(container string, options types.CheckpointCreateOptions) error
checkpointDeleteFunc func(container string, options types.CheckpointDeleteOptions) error
checkpointListFunc func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error)
}
func (cli *fakeClient) CheckpointCreate(ctx context.Context, container string, options types.CheckpointCreateOptions) error {
if cli.checkpointCreateFunc != nil {
return cli.checkpointCreateFunc(container, options)
}
return nil
}
func (cli *fakeClient) CheckpointDelete(ctx context.Context, container string, options types.CheckpointDeleteOptions) error {
if cli.checkpointDeleteFunc != nil {
return cli.checkpointDeleteFunc(container, options)
}
return nil
}
func (cli *fakeClient) CheckpointList(ctx context.Context, container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
if cli.checkpointListFunc != nil {
return cli.checkpointListFunc(container, options)
}
return []types.Checkpoint{}, nil
}

View File

@ -1,72 +0,0 @@
package checkpoint
import (
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestCheckpointCreateErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointCreateFunc func(container string, options types.CheckpointCreateOptions) error
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"foo", "bar"},
checkpointCreateFunc: func(container string, options types.CheckpointCreateOptions) error {
return errors.Errorf("error creating checkpoint for container foo")
},
expectedError: "error creating checkpoint for container foo",
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{
checkpointCreateFunc: tc.checkpointCreateFunc,
})
cmd := newCreateCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestCheckpointCreateWithOptions(t *testing.T) {
var containerID, checkpointID, checkpointDir string
var exit bool
cli := test.NewFakeCli(&fakeClient{
checkpointCreateFunc: func(container string, options types.CheckpointCreateOptions) error {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir
exit = options.Exit
return nil
},
})
cmd := newCreateCommand(cli)
checkpoint := "checkpoint-bar"
cmd.SetArgs([]string{"container-foo", checkpoint})
cmd.Flags().Set("leave-running", "true")
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "container-foo", containerID)
assert.Equal(t, checkpoint, checkpointID)
assert.Equal(t, "/dir/foo", checkpointDir)
assert.Equal(t, false, exit)
assert.Equal(t, checkpoint, strings.TrimSpace(cli.OutBuffer().String()))
}

View File

@ -1,71 +0,0 @@
package checkpoint
import (
"bytes"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestCheckpointListErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointListFunc func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error)
expectedError string
}{
{
args: []string{},
expectedError: "requires exactly 1 argument",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires exactly 1 argument",
},
{
args: []string{"foo"},
checkpointListFunc: func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
return []types.Checkpoint{}, errors.Errorf("error getting checkpoints for container foo")
},
expectedError: "error getting checkpoints for container foo",
},
}
for _, tc := range testCases {
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointListFunc: tc.checkpointListFunc,
}, &bytes.Buffer{})
cmd := newListCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestCheckpointListWithOptions(t *testing.T) {
var containerID, checkpointDir string
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointListFunc: func(container string, options types.CheckpointListOptions) ([]types.Checkpoint, error) {
containerID = container
checkpointDir = options.CheckpointDir
return []types.Checkpoint{
{Name: "checkpoint-foo"},
}, nil
},
}, buf)
cmd := newListCommand(cli)
cmd.SetArgs([]string{"container-foo"})
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "container-foo", containerID)
assert.Equal(t, "/dir/foo", checkpointDir)
actual := buf.String()
expected := golden.Get(t, []byte(actual), "checkpoint-list-with-options.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -1,66 +0,0 @@
package checkpoint
import (
"bytes"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)
func TestCheckpointRemoveErrors(t *testing.T) {
testCases := []struct {
args []string
checkpointDeleteFunc func(container string, options types.CheckpointDeleteOptions) error
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"too", "many", "arguments"},
expectedError: "requires exactly 2 argument(s)",
},
{
args: []string{"foo", "bar"},
checkpointDeleteFunc: func(container string, options types.CheckpointDeleteOptions) error {
return errors.Errorf("error deleting checkpoint")
},
expectedError: "error deleting checkpoint",
},
}
for _, tc := range testCases {
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointDeleteFunc: tc.checkpointDeleteFunc,
}, &bytes.Buffer{})
cmd := newRemoveCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestCheckpointRemoveWithOptions(t *testing.T) {
var containerID, checkpointID, checkpointDir string
cli := test.NewFakeCliWithOutput(&fakeClient{
checkpointDeleteFunc: func(container string, options types.CheckpointDeleteOptions) error {
containerID = container
checkpointID = options.CheckpointID
checkpointDir = options.CheckpointDir
return nil
},
}, &bytes.Buffer{})
cmd := newRemoveCommand(cli)
cmd.SetArgs([]string{"container-foo", "checkpoint-bar"})
cmd.Flags().Set("checkpoint-dir", "/dir/foo")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "container-foo", containerID)
assert.Equal(t, "checkpoint-bar", checkpointID)
assert.Equal(t, "/dir/foo", checkpointDir)
}

View File

@ -1,2 +0,0 @@
CHECKPOINT NAME
checkpoint-foo

View File

@ -1,19 +1,21 @@
package command
import (
"fmt"
"io"
"net"
"net/http"
"os"
"runtime"
"time"
"github.com/docker/cli/cli"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/credentials"
cliflags "github.com/docker/cli/cli/flags"
dopts "github.com/docker/cli/opts"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/docker/go-connections/sockets"
"github.com/docker/go-connections/tlsconfig"
@ -38,7 +40,7 @@ type Cli interface {
In() *InStream
SetIn(in *InStream)
ConfigFile() *configfile.ConfigFile
ServerInfo() ServerInfo
CredentialsStore(serverAddress string) credentials.Store
}
// DockerCli is an instance the docker command line client.
@ -103,10 +105,59 @@ func (cli *DockerCli) ServerInfo() ServerInfo {
return cli.server
}
// GetAllCredentials returns all of the credentials stored in all of the
// configured credential stores.
func (cli *DockerCli) GetAllCredentials() (map[string]types.AuthConfig, error) {
auths := make(map[string]types.AuthConfig)
for registry := range cli.configFile.CredentialHelpers {
helper := cli.CredentialsStore(registry)
newAuths, err := helper.GetAll()
if err != nil {
return nil, err
}
addAll(auths, newAuths)
}
defaultStore := cli.CredentialsStore("")
newAuths, err := defaultStore.GetAll()
if err != nil {
return nil, err
}
addAll(auths, newAuths)
return auths, nil
}
func addAll(to, from map[string]types.AuthConfig) {
for reg, ac := range from {
to[reg] = ac
}
}
// CredentialsStore returns a new credentials store based
// on the settings provided in the configuration file. Empty string returns
// the default credential store.
func (cli *DockerCli) CredentialsStore(serverAddress string) credentials.Store {
if helper := getConfiguredCredentialStore(cli.configFile, serverAddress); helper != "" {
return credentials.NewNativeStore(cli.configFile, helper)
}
return credentials.NewFileStore(cli.configFile)
}
// getConfiguredCredentialStore returns the credential helper configured for the
// given registry, the default credsStore, or the empty string if neither are
// configured.
func getConfiguredCredentialStore(c *configfile.ConfigFile, serverAddress string) string {
if c.CredentialHelpers != nil && serverAddress != "" {
if helper, exists := c.CredentialHelpers[serverAddress]; exists {
return helper
}
}
return c.CredentialsStore
}
// Initialize the dockerCli runs initialization that must happen after command
// line flags are parsed.
func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
cli.configFile = cliconfig.LoadDefaultConfigFile(cli.err)
cli.configFile = LoadDefaultConfigFile(cli.err)
var err error
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
@ -142,10 +193,15 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
OSType: ping.OSType,
}
cli.client.NegotiateAPIVersionPing(ping)
} else {
// Default to true if we fail to connect to daemon
cli.server = ServerInfo{HasExperimental: true}
// since the new header was added in 1.25, assume server is 1.24 if header is not present.
if ping.APIVersion == "" {
ping.APIVersion = "1.24"
}
// if server version is lower than the current cli, downgrade
if versions.LessThan(ping.APIVersion, cli.client.ClientVersion()) {
cli.client.UpdateClientVersion(ping.APIVersion)
}
}
return nil
@ -163,6 +219,19 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
}
// LoadDefaultConfigFile attempts to load the default config file and returns
// an initialized ConfigFile struct if none is found.
func LoadDefaultConfigFile(err io.Writer) *configfile.ConfigFile {
configFile, e := cliconfig.Load(cliconfig.Dir())
if e != nil {
fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
}
if !configFile.ContainsAuth() {
credentials.DetectDefaultStore(configFile)
}
return configFile
}
// NewAPIClientFromFlags creates a new APIClient from command line flags
func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.ConfigFile) (client.APIClient, error) {
host, err := getServerHost(opts.Hosts, opts.TLSOptions)
@ -216,10 +285,6 @@ func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, er
}
tr := &http.Transport{
TLSClientConfig: config,
DialContext: (&net.Dialer{
KeepAlive: 30 * time.Second,
Timeout: 30 * time.Second,
}).DialContext,
}
proto, addr, _, err := client.ParseHost(host)
if err != nil {

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/pkg/system"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -64,7 +65,7 @@ func runConfigCreate(dockerCli command.Cli, options createOptions) error {
spec := swarm.ConfigSpec{
Annotations: swarm.Annotations{
Name: options.name,
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
},
Data: configData,
}

View File

@ -1,6 +1,7 @@
package config
import (
"bytes"
"io/ioutil"
"path/filepath"
"reflect"
@ -40,10 +41,11 @@ func TestConfigCreateErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigCreateCommand(
test.NewFakeCli(&fakeClient{
configCreateFunc: tc.configCreateFunc,
}),
}, buf),
)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
@ -53,6 +55,7 @@ func TestConfigCreateErrors(t *testing.T) {
func TestConfigCreateWithName(t *testing.T) {
name := "foo"
buf := new(bytes.Buffer)
var actual []byte
cli := test.NewFakeCli(&fakeClient{
configCreateFunc: func(spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
@ -66,14 +69,14 @@ func TestConfigCreateWithName(t *testing.T) {
ID: "ID-" + spec.Name,
}, nil
},
})
}, buf)
cmd := newConfigCreateCommand(cli)
cmd.SetArgs([]string{name, filepath.Join("testdata", configDataFile)})
assert.NoError(t, cmd.Execute())
expected := golden.Get(t, actual, configDataFile)
assert.Equal(t, string(expected), string(actual))
assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String()))
assert.Equal(t, "ID-"+name, strings.TrimSpace(buf.String()))
}
func TestConfigCreateWithLabels(t *testing.T) {
@ -83,6 +86,7 @@ func TestConfigCreateWithLabels(t *testing.T) {
}
name := "foo"
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configCreateFunc: func(spec swarm.ConfigSpec) (types.ConfigCreateResponse, error) {
if spec.Name != name {
@ -97,12 +101,12 @@ func TestConfigCreateWithLabels(t *testing.T) {
ID: "ID-" + spec.Name,
}, nil
},
})
}, buf)
cmd := newConfigCreateCommand(cli)
cmd.SetArgs([]string{name, filepath.Join("testdata", configDataFile)})
cmd.Flags().Set("label", "lbl1=Label-foo")
cmd.Flags().Set("label", "lbl2=Label-bar")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "ID-"+name, strings.TrimSpace(cli.OutBuffer().String()))
assert.Equal(t, "ID-"+name, strings.TrimSpace(buf.String()))
}

View File

@ -55,7 +55,7 @@ func TestConfigInspectErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf),
)
@ -97,7 +97,7 @@ func TestConfigInspectWithoutFormat(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf),
)
@ -137,7 +137,7 @@ func TestConfigInspectWithFormat(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf),
)
@ -174,7 +174,7 @@ func TestConfigInspectPretty(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
configInspectFunc: tc.configInspectFunc,
}, buf))
cmd.SetArgs([]string{"configID"})

View File

@ -1,6 +1,7 @@
package config
import (
"bytes"
"io/ioutil"
"testing"
"time"
@ -35,10 +36,11 @@ func TestConfigListErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigListCommand(
test.NewFakeCli(&fakeClient{
configListFunc: tc.configListFunc,
}),
}, buf),
)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
@ -47,6 +49,7 @@ func TestConfigListErrors(t *testing.T) {
}
func TestConfigList(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -64,16 +67,18 @@ func TestConfigList(t *testing.T) {
),
}, nil
},
})
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newConfigListCommand(cli)
cmd.SetOutput(cli.OutBuffer())
cmd.SetOutput(buf)
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
actual := buf.String()
expected := golden.Get(t, []byte(actual), "config-list.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithQuietOption(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -83,16 +88,18 @@ func TestConfigListWithQuietOption(t *testing.T) {
})),
}, nil
},
})
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newConfigListCommand(cli)
cmd.Flags().Set("quiet", "true")
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
actual := buf.String()
expected := golden.Get(t, []byte(actual), "config-list-with-quiet-option.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithConfigFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -102,18 +109,19 @@ func TestConfigListWithConfigFormat(t *testing.T) {
})),
}, nil
},
})
cli.SetConfigFile(&configfile.ConfigFile{
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{
ConfigFormat: "{{ .Name }} {{ .Labels }}",
})
cmd := newConfigListCommand(cli)
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
actual := buf.String()
expected := golden.Get(t, []byte(actual), "config-list-with-config-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
return []swarm.Config{
@ -123,16 +131,17 @@ func TestConfigListWithFormat(t *testing.T) {
})),
}, nil
},
})
}, buf)
cmd := newConfigListCommand(cli)
cmd.Flags().Set("format", "{{ .Name }} {{ .Labels }}")
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
actual := buf.String()
expected := golden.Get(t, []byte(actual), "config-list-with-format.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}
func TestConfigListWithFilter(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
assert.Equal(t, "foo", options.Filters.Get("name")[0])
@ -152,12 +161,13 @@ func TestConfigListWithFilter(t *testing.T) {
),
}, nil
},
})
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newConfigListCommand(cli)
cmd.Flags().Set("filter", "name=foo")
cmd.Flags().Set("filter", "label=lbl1=Label-bar")
assert.NoError(t, cmd.Execute())
actual := cli.OutBuffer().String()
actual := buf.String()
expected := golden.Get(t, []byte(actual), "config-list-with-filter.golden")
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, string(expected))
}

View File

@ -33,7 +33,7 @@ func TestConfigRemoveErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newConfigRemoveCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
configRemoveFunc: tc.configRemoveFunc,
}, buf),
)
@ -47,7 +47,7 @@ func TestConfigRemoveWithName(t *testing.T) {
names := []string{"foo", "bar"}
buf := new(bytes.Buffer)
var removedConfigs []string
cli := test.NewFakeCliWithOutput(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
configRemoveFunc: func(name string) error {
removedConfigs = append(removedConfigs, name)
return nil
@ -65,7 +65,7 @@ func TestConfigRemoveContinueAfterError(t *testing.T) {
buf := new(bytes.Buffer)
var removedConfigs []string
cli := test.NewFakeCliWithOutput(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
configRemoveFunc: func(name string) error {
removedConfigs = append(removedConfigs, name)
if name == "foo" {
@ -77,7 +77,6 @@ func TestConfigRemoveContinueAfterError(t *testing.T) {
cmd := newConfigRemoveCommand(cli)
cmd.SetArgs(names)
cmd.SetOutput(ioutil.Discard)
assert.EqualError(t, cmd.Execute(), "error removing config: foo")
assert.Equal(t, names, removedConfigs)
}

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/signal"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -23,26 +22,8 @@ type attachOptions struct {
container string
}
func inspectContainerAndCheckState(ctx context.Context, cli client.APIClient, args string) (*types.ContainerJSON, error) {
c, err := cli.ContainerInspect(ctx, args)
if err != nil {
return nil, err
}
if !c.State.Running {
return nil, errors.New("You cannot attach to a stopped container, start it first")
}
if c.State.Paused {
return nil, errors.New("You cannot attach to a paused container, unpause it first")
}
if c.State.Restarting {
return nil, errors.New("You cannot attach to a restarting container, wait until it is running")
}
return &c, nil
}
// NewAttachCommand creates a new cobra.Command for `docker attach`
func NewAttachCommand(dockerCli command.Cli) *cobra.Command {
func NewAttachCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts attachOptions
cmd := &cobra.Command{
@ -62,15 +43,23 @@ func NewAttachCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runAttach(dockerCli command.Cli, opts *attachOptions) error {
func runAttach(dockerCli *command.DockerCli, opts *attachOptions) error {
ctx := context.Background()
client := dockerCli.Client()
c, err := inspectContainerAndCheckState(ctx, client, opts.container)
c, err := client.ContainerInspect(ctx, opts.container)
if err != nil {
return err
}
if !c.State.Running {
return errors.New("You cannot attach to a stopped container, start it first")
}
if c.State.Paused {
return errors.New("You cannot attach to a paused container, unpause it first")
}
if err := dockerCli.In().CheckTty(!opts.noStdin, c.Config.Tty); err != nil {
return err
}
@ -106,19 +95,6 @@ func runAttach(dockerCli command.Cli, opts *attachOptions) error {
}
defer resp.Close()
// If use docker attach command to attach to a stop container, it will return
// "You cannot attach to a stopped container" error, it's ok, but when
// attach to a running container, it(docker attach) use inspect to check
// the container's state, if it pass the state check on the client side,
// and then the container is stopped, docker attach command still attach to
// the container and not exit.
//
// Recheck the container's state to avoid attach block.
_, err = inspectContainerAndCheckState(ctx, client, opts.container)
if err != nil {
return err
}
if c.Config.Tty && dockerCli.Out().IsTerminal() {
height, width := dockerCli.Out().GetTtySize()
// To handle the case where a user repeatedly attaches/detaches without resizing their

View File

@ -1,75 +0,0 @@
package container
import (
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
)
func TestNewAttachCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
containerInspectFunc func(img string) (types.ContainerJSON, error)
}{
{
name: "client-error",
args: []string{"5cb5bb5e4a3b"},
expectedError: "something went wrong",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
return types.ContainerJSON{}, errors.Errorf("something went wrong")
},
},
{
name: "client-stopped",
args: []string{"5cb5bb5e4a3b"},
expectedError: "You cannot attach to a stopped container",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
c := types.ContainerJSON{}
c.ContainerJSONBase = &types.ContainerJSONBase{}
c.ContainerJSONBase.State = &types.ContainerState{Running: false}
return c, nil
},
},
{
name: "client-paused",
args: []string{"5cb5bb5e4a3b"},
expectedError: "You cannot attach to a paused container",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
c := types.ContainerJSON{}
c.ContainerJSONBase = &types.ContainerJSONBase{}
c.ContainerJSONBase.State = &types.ContainerState{
Running: true,
Paused: true,
}
return c, nil
},
},
{
name: "client-restarting",
args: []string{"5cb5bb5e4a3b"},
expectedError: "You cannot attach to a restarting container",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
c := types.ContainerJSON{}
c.ContainerJSONBase = &types.ContainerJSONBase{}
c.ContainerJSONBase.State = &types.ContainerState{
Running: true,
Paused: false,
Restarting: true,
}
return c, nil
},
},
}
for _, tc := range testCases {
cmd := NewAttachCommand(test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc}))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}

View File

@ -1,19 +0,0 @@
package container
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
containerInspectFunc func(string) (types.ContainerJSON, error)
}
func (cli *fakeClient) ContainerInspect(_ context.Context, containerID string) (types.ContainerJSON, error) {
if cli.containerInspectFunc != nil {
return cli.containerInspectFunc(containerID)
}
return types.ContainerJSON{}, nil
}

View File

@ -33,7 +33,7 @@ func newExecOptions() *execOptions {
}
// NewExecCommand creates a new cobra.Command for `docker exec`
func NewExecCommand(dockerCli command.Cli) *cobra.Command {
func NewExecCommand(dockerCli *command.DockerCli) *cobra.Command {
options := newExecOptions()
cmd := &cobra.Command{
@ -63,7 +63,7 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
}
// nolint: gocyclo
func runExec(dockerCli command.Cli, options *execOptions, container string, execCmd []string) error {
func runExec(dockerCli *command.DockerCli, options *execOptions, container string, execCmd []string) error {
execConfig, err := parseExec(options, execCmd)
// just in case the ParseExec does not exit
if container == "" || err != nil {
@ -111,7 +111,12 @@ func runExec(dockerCli command.Cli, options *execOptions, container string, exec
Tty: execConfig.Tty,
}
return client.ContainerExecStart(ctx, execID, execStartCheck)
if err := client.ContainerExecStart(ctx, execID, execStartCheck); err != nil {
return err
}
// For now don't print this - wait for when we support exec wait()
// fmt.Fprintf(dockerCli.Out(), "%s\n", execID)
return nil
}
// Interactive exec requested.

View File

@ -1,14 +1,9 @@
package container
import (
"io/ioutil"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)
type arguments struct {
@ -78,7 +73,9 @@ func TestParseExec(t *testing.T) {
for valid, expectedExecConfig := range valids {
execConfig, err := parseExec(&valid.options, valid.execCmd)
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
if !compareExecConfig(expectedExecConfig, execConfig) {
t.Fatalf("Expected [%v] for %v, got [%v]", expectedExecConfig, valid, execConfig)
}
@ -117,28 +114,3 @@ func compareExecConfig(config1 *types.ExecConfig, config2 *types.ExecConfig) boo
}
return true
}
func TestNewExecCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
containerInspectFunc func(img string) (types.ContainerJSON, error)
}{
{
name: "client-error",
args: []string{"5cb5bb5e4a3b", "-t", "-i", "bash"},
expectedError: "something went wrong",
containerInspectFunc: func(containerID string) (types.ContainerJSON, error) {
return types.ContainerJSON{}, errors.Errorf("something went wrong")
},
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{containerInspectFunc: tc.containerInspectFunc})
cmd := NewExecCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}

View File

@ -110,7 +110,7 @@ func (h *hijackedIOStreamer) setupInput() (restore func(), err error) {
func (h *hijackedIOStreamer) beginOutputStream(restoreInput func()) <-chan error {
if h.outputStream == nil && h.errorStream == nil {
// There is no need to copy output.
// Ther is no need to copy output.
return nil
}
@ -163,7 +163,7 @@ func (h *hijackedIOStreamer) beginInputStream(restoreInput func()) (doneC <-chan
if err != nil {
// This error will also occur on the receive
// side (from stdout) where it will be
// propagated back to the caller.
// propogated back to the caller.
logrus.Debugf("Error sendStdin: %s", err)
}
}

View File

@ -12,19 +12,19 @@ import (
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/container"
networktypes "github.com/docker/docker/api/types/network"
"github.com/docker/docker/api/types/strslice"
"github.com/docker/docker/pkg/signal"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/spf13/pflag"
)
var (
deviceCgroupRuleRegexp = regexp.MustCompile(`^[acb] ([0-9]+|\*):([0-9]+|\*) [rwm]{1,3}$`)
deviceCgroupRuleRegexp = regexp.MustCompile("^[acb] ([0-9]+|\\*):([0-9]+|\\*) [rwm]{1,3}$")
)
// containerOptions is a data object with all the options for creating a container
@ -333,8 +333,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
volumes := copts.volumes.GetMap()
// add any bind targets to the list of container volumes
for bind := range copts.volumes.GetMap() {
parsed, _ := loader.ParseVolume(bind)
if parsed.Source != "" {
if arr := volumeSplitN(bind, 2); len(arr) > 1 {
// after creating the bind mount we want to delete it from the copts.volumes values because
// we do not want bind mounts being committed to image configs
binds = append(binds, bind)
@ -410,13 +409,13 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
}
// collect all the environment variables for the container
envVariables, err := opts.ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll())
envVariables, err := runconfigopts.ReadKVStrings(copts.envFile.GetAll(), copts.env.GetAll())
if err != nil {
return nil, err
}
// collect all the labels for the container
labels, err := opts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll())
labels, err := runconfigopts.ReadKVStrings(copts.labelsFile.GetAll(), copts.labels.GetAll())
if err != nil {
return nil, err
}
@ -441,7 +440,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
return nil, errors.Errorf("--userns: invalid USER mode")
}
restartPolicy, err := opts.ParseRestartPolicy(copts.restartPolicy)
restartPolicy, err := runconfigopts.ParseRestartPolicy(copts.restartPolicy)
if err != nil {
return nil, err
}
@ -554,7 +553,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
MacAddress: copts.macAddress,
Entrypoint: entrypoint,
WorkingDir: copts.workingDir,
Labels: opts.ConvertKVStringsToMap(labels),
Labels: runconfigopts.ConvertKVStringsToMap(labels),
Healthcheck: healthConfig,
}
if flags.Changed("stop-signal") {
@ -667,7 +666,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*containerConfig, err
}
func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) {
loggingOptsMap := opts.ConvertKVStringsToMap(loggingOpts)
loggingOptsMap := runconfigopts.ConvertKVStringsToMap(loggingOpts)
if loggingDriver == "none" && len(loggingOpts) > 0 {
return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", loggingDriver)
}
@ -829,6 +828,67 @@ func validatePath(val string, validator func(string) bool) (string, error) {
return val, nil
}
// volumeSplitN splits raw into a maximum of n parts, separated by a separator colon.
// A separator colon is the last `:` character in the regex `[:\\]?[a-zA-Z]:` (note `\\` is `\` escaped).
// In Windows driver letter appears in two situations:
// a. `^[a-zA-Z]:` (A colon followed by `^[a-zA-Z]:` is OK as colon is the separator in volume option)
// b. A string in the format like `\\?\C:\Windows\...` (UNC).
// Therefore, a driver letter can only follow either a `:` or `\\`
// This allows to correctly split strings such as `C:\foo:D:\:rw` or `/tmp/q:/foo`.
func volumeSplitN(raw string, n int) []string {
var array []string
if len(raw) == 0 || raw[0] == ':' {
// invalid
return nil
}
// numberOfParts counts the number of parts separated by a separator colon
numberOfParts := 0
// left represents the left-most cursor in raw, updated at every `:` character considered as a separator.
left := 0
// right represents the right-most cursor in raw incremented with the loop. Note this
// starts at index 1 as index 0 is already handle above as a special case.
for right := 1; right < len(raw); right++ {
// stop parsing if reached maximum number of parts
if n >= 0 && numberOfParts >= n {
break
}
if raw[right] != ':' {
continue
}
potentialDriveLetter := raw[right-1]
if (potentialDriveLetter >= 'A' && potentialDriveLetter <= 'Z') || (potentialDriveLetter >= 'a' && potentialDriveLetter <= 'z') {
if right > 1 {
beforePotentialDriveLetter := raw[right-2]
// Only `:` or `\\` are checked (`/` could fall into the case of `/tmp/q:/foo`)
if beforePotentialDriveLetter != ':' && beforePotentialDriveLetter != '\\' {
// e.g. `C:` is not preceded by any delimiter, therefore it was not a drive letter but a path ending with `C:`.
array = append(array, raw[left:right])
left = right + 1
numberOfParts++
}
// else, `C:` is considered as a drive letter and not as a delimiter, so we continue parsing.
}
// if right == 1, then `C:` is the beginning of the raw string, therefore `:` is again not considered a delimiter and we continue parsing.
} else {
// if `:` is not preceded by a potential drive letter, then consider it as a delimiter.
array = append(array, raw[left:right])
left = right + 1
numberOfParts++
}
}
// need to take care of the last part
if left < len(raw) {
if n >= 0 && numberOfParts >= n {
// if the maximum number of parts is reached, just append the rest to the last part
// left-1 is at the last `:` that needs to be included since not considered a separator.
array[n-1] += raw[left-1:]
} else {
array = append(array, raw[left:])
}
}
return array
}
// validateAttach validates that the specified string is a valid attach option.
func validateAttach(val string) (string, error) {
s := strings.ToLower(val)

View File

@ -19,7 +19,6 @@ import (
"github.com/pkg/errors"
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestValidateAttach(t *testing.T) {
@ -62,14 +61,16 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *network
return containerConfig.Config, containerConfig.HostConfig, containerConfig.NetworkingConfig, err
}
func parseMustError(t *testing.T, args string) {
_, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
assert.Error(t, err, args)
func parsetest(t *testing.T, args string) (*container.Config, *container.HostConfig, error) {
config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
return config, hostConfig, err
}
func mustParse(t *testing.T, args string) (*container.Config, *container.HostConfig) {
config, hostConfig, _, err := parseRun(append(strings.Split(args, " "), "ubuntu", "bash"))
assert.NoError(t, err)
config, hostConfig, err := parsetest(t, args)
if err != nil {
t.Fatal(err)
}
return config, hostConfig
}
@ -85,6 +86,7 @@ func TestParseRunLinks(t *testing.T) {
}
}
// nolint: gocyclo
func TestParseRunAttach(t *testing.T) {
if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
@ -101,17 +103,31 @@ func TestParseRunAttach(t *testing.T) {
if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
}
}
func TestParseRunWithInvalidArgs(t *testing.T) {
parseMustError(t, "-a")
parseMustError(t, "-a invalid")
parseMustError(t, "-a invalid -a stdout")
parseMustError(t, "-a stdout -a stderr -d")
parseMustError(t, "-a stdin -d")
parseMustError(t, "-a stdout -d")
parseMustError(t, "-a stderr -d")
parseMustError(t, "-d --rm")
if _, _, err := parsetest(t, "-a"); err == nil {
t.Fatal("Error parsing attach flags, `-a` should be an error but is not")
}
if _, _, err := parsetest(t, "-a invalid"); err == nil {
t.Fatal("Error parsing attach flags, `-a invalid` should be an error but is not")
}
if _, _, err := parsetest(t, "-a invalid -a stdout"); err == nil {
t.Fatal("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
}
if _, _, err := parsetest(t, "-a stdout -a stderr -d"); err == nil {
t.Fatal("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
}
if _, _, err := parsetest(t, "-a stdin -d"); err == nil {
t.Fatal("Error parsing attach flags, `-a stdin -d` should be an error but is not")
}
if _, _, err := parsetest(t, "-a stdout -d"); err == nil {
t.Fatal("Error parsing attach flags, `-a stdout -d` should be an error but is not")
}
if _, _, err := parsetest(t, "-a stderr -d"); err == nil {
t.Fatal("Error parsing attach flags, `-a stderr -d` should be an error but is not")
}
if _, _, err := parsetest(t, "-d --rm"); err == nil {
t.Fatal("Error parsing attach flags, `-d --rm` should be an error but is not")
}
}
// nolint: gocyclo
@ -367,46 +383,51 @@ func TestParseDevice(t *testing.T) {
func TestParseModes(t *testing.T) {
// ipc ko
_, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"})
testutil.ErrorContains(t, err, "--ipc: invalid IPC mode")
if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
}
// ipc ok
_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
if !hostconfig.IpcMode.Valid() {
t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
}
// pid ko
_, _, _, err = parseRun([]string{"--pid=container:", "img", "cmd"})
testutil.ErrorContains(t, err, "--pid: invalid PID mode")
if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
}
// pid ok
_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
if !hostconfig.PidMode.Valid() {
t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
}
// uts ko
_, _, _, err = parseRun([]string{"--uts=container:", "img", "cmd"})
testutil.ErrorContains(t, err, "--uts: invalid UTS mode")
if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
}
// uts ok
_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
if !hostconfig.UTSMode.Valid() {
t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
}
// shm-size ko
expectedErr := `invalid argument "a128m" for --shm-size=a128m: invalid size: 'a128m'`
_, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"})
testutil.ErrorContains(t, err, expectedErr)
if _, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"}); err == nil || err.Error() != expectedErr {
t.Fatalf("Expected an error with message '%v', got %v", expectedErr, err)
}
// shm-size ok
_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
require.NoError(t, err)
if err != nil {
t.Fatal(err)
}
if hostconfig.ShmSize != 134217728 {
t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize)
}
@ -750,6 +771,58 @@ func callDecodeContainerConfig(volumes []string, binds []string) (*container.Con
return c, h, err
}
func TestVolumeSplitN(t *testing.T) {
for _, x := range []struct {
input string
n int
expected []string
}{
{`C:\foo:d:`, -1, []string{`C:\foo`, `d:`}},
{`:C:\foo:d:`, -1, nil},
{`/foo:/bar:ro`, 3, []string{`/foo`, `/bar`, `ro`}},
{`/foo:/bar:ro`, 2, []string{`/foo`, `/bar:ro`}},
{`C:\foo\:/foo`, -1, []string{`C:\foo\`, `/foo`}},
{`d:\`, -1, []string{`d:\`}},
{`d:`, -1, []string{`d:`}},
{`d:\path`, -1, []string{`d:\path`}},
{`d:\path with space`, -1, []string{`d:\path with space`}},
{`d:\pathandmode:rw`, -1, []string{`d:\pathandmode`, `rw`}},
{`c:\:d:\`, -1, []string{`c:\`, `d:\`}},
{`c:\windows\:d:`, -1, []string{`c:\windows\`, `d:`}},
{`c:\windows:d:\s p a c e`, -1, []string{`c:\windows`, `d:\s p a c e`}},
{`c:\windows:d:\s p a c e:RW`, -1, []string{`c:\windows`, `d:\s p a c e`, `RW`}},
{`c:\program files:d:\s p a c e i n h o s t d i r`, -1, []string{`c:\program files`, `d:\s p a c e i n h o s t d i r`}},
{`0123456789name:d:`, -1, []string{`0123456789name`, `d:`}},
{`MiXeDcAsEnAmE:d:`, -1, []string{`MiXeDcAsEnAmE`, `d:`}},
{`name:D:`, -1, []string{`name`, `D:`}},
{`name:D::rW`, -1, []string{`name`, `D:`, `rW`}},
{`name:D::RW`, -1, []string{`name`, `D:`, `RW`}},
{`c:/:d:/forward/slashes/are/good/too`, -1, []string{`c:/`, `d:/forward/slashes/are/good/too`}},
{`c:\Windows`, -1, []string{`c:\Windows`}},
{`c:\Program Files (x86)`, -1, []string{`c:\Program Files (x86)`}},
{``, -1, nil},
{`.`, -1, []string{`.`}},
{`..\`, -1, []string{`..\`}},
{`c:\:..\`, -1, []string{`c:\`, `..\`}},
{`c:\:d:\:xyzzy`, -1, []string{`c:\`, `d:\`, `xyzzy`}},
// Cover directories with one-character name
{`/tmp/x/y:/foo/x/y`, -1, []string{`/tmp/x/y`, `/foo/x/y`}},
} {
res := volumeSplitN(x.input, x.n)
if len(res) < len(x.expected) {
t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
}
for i, e := range res {
if e != x.expected[i] {
t.Fatalf("input: %v, expected: %v, got: %v", x.input, x.expected, res)
}
}
}
}
func TestValidateDevice(t *testing.T) {
valid := []string{
"/home",

View File

@ -5,7 +5,6 @@ import (
"io"
"net/http/httputil"
"os"
"regexp"
"runtime"
"strings"
"syscall"
@ -13,12 +12,12 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/term"
"github.com/docker/libnetwork/resolvconf/dns"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -78,43 +77,21 @@ func warnOnOomKillDisable(hostConfig container.HostConfig, stderr io.Writer) {
// they are trying to set a DNS to a localhost address
func warnOnLocalhostDNS(hostConfig container.HostConfig, stderr io.Writer) {
for _, dnsIP := range hostConfig.DNS {
if isLocalhost(dnsIP) {
if dns.IsLocalhost(dnsIP) {
fmt.Fprintf(stderr, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
return
}
}
}
// IPLocalhost is a regex pattern for IPv4 or IPv6 loopback range.
const ipLocalhost = `((127\.([0-9]{1,3}\.){2}[0-9]{1,3})|(::1)$)`
var localhostIPRegexp = regexp.MustCompile(ipLocalhost)
// IsLocalhost returns true if ip matches the localhost IP regular expression.
// Used for determining if nameserver settings are being passed which are
// localhost addresses
func isLocalhost(ip string) bool {
return localhostIPRegexp.MatchString(ip)
}
func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), copts.env.GetAll())
newEnv := []string{}
for k, v := range proxyConfig {
if v == nil {
newEnv = append(newEnv, k)
} else {
newEnv = append(newEnv, fmt.Sprintf("%s=%s", k, *v))
}
}
copts.env = *opts.NewListOptsRef(&newEnv, nil)
func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *runOptions, copts *containerOptions) error {
containerConfig, err := parse(flags, copts)
// just in case the parse does not exit
if err != nil {
reportError(dockerCli.Err(), "run", err.Error(), true)
return cli.StatusError{StatusCode: 125}
}
return runContainer(dockerCli, ropts, copts, containerConfig)
return runContainer(dockerCli, opts, copts, containerConfig)
}
// nolint: gocyclo
@ -170,7 +147,6 @@ func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *contain
sigc := ForwardAllSignals(ctx, dockerCli, createResponse.ID)
defer signal.StopCatch(sigc)
}
var (
waitDisplayID chan struct{}
errCh chan error
@ -190,11 +166,10 @@ func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *contain
}
close, err := attachContainer(ctx, dockerCli, &errCh, config, createResponse.ID)
defer close()
if err != nil {
return err
}
defer close()
}
statusChan := waitExitOrRemoved(ctx, dockerCli, createResponse.ID, copts.autoRemove)

View File

@ -146,12 +146,12 @@ func runStart(dockerCli *command.DockerCli, opts *startOptions) error {
fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
}
}
if attachErr := <-cErr; attachErr != nil {
if attchErr := <-cErr; attchErr != nil {
if _, ok := err.(term.EscapeError); ok {
// The user entered the detach escape sequence.
return nil
}
return attachErr
return attchErr
}
if status := <-statusChan; status != 0 {

View File

@ -106,7 +106,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
closeChan <- err
}
for _, container := range cs {
s := formatter.NewContainerStats(container.ID[:12])
s := formatter.NewContainerStats(container.ID[:12], daemonOSType)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
@ -123,7 +123,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
eh := command.InitEventHandler()
eh.Handle("create", func(e events.Message) {
if opts.all {
s := formatter.NewContainerStats(e.ID[:12])
s := formatter.NewContainerStats(e.ID[:12], daemonOSType)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
@ -132,7 +132,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
})
eh.Handle("start", func(e events.Message) {
s := formatter.NewContainerStats(e.ID[:12])
s := formatter.NewContainerStats(e.ID[:12], daemonOSType)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)
@ -158,7 +158,7 @@ func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
// Artificially send creation events for the containers we were asked to
// monitor (same code path than we use when monitoring all containers).
for _, name := range opts.containers {
s := formatter.NewContainerStats(name)
s := formatter.NewContainerStats(name, daemonOSType)
if cStats.add(s) {
waitFirst.Add(1)
go collect(ctx, s, dockerCli.Client(), !opts.noStream, waitFirst)

View File

@ -16,8 +16,9 @@ import (
)
type stats struct {
mu sync.Mutex
cs []*formatter.ContainerStats
ostype string
mu sync.Mutex
cs []*formatter.ContainerStats
}
// daemonOSType is set once we have at least one stat for a container

View File

@ -39,7 +39,7 @@ func resizeTtyTo(ctx context.Context, client client.ContainerAPIClient, id strin
}
// MonitorTtySize updates the container tty size when the terminal tty changes size
func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool) error {
func MonitorTtySize(ctx context.Context, cli *command.DockerCli, id string, isExec bool) error {
resizeTty := func() {
height, width := cli.Out().GetTtySize()
resizeTtyTo(ctx, cli.Client(), id, height, width, isExec)
@ -74,7 +74,7 @@ func MonitorTtySize(ctx context.Context, cli command.Cli, id string, isExec bool
}
// ForwardAllSignals forwards signals to the container
func ForwardAllSignals(ctx context.Context, cli command.Cli, cid string) chan os.Signal {
func ForwardAllSignals(ctx context.Context, cli *command.DockerCli, cid string) chan os.Signal {
sigc := make(chan os.Signal, 128)
signal.CatchAll(sigc)
go func() {

View File

@ -8,6 +8,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
containertypes "github.com/docker/docker/api/types/container"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -81,7 +82,7 @@ func runUpdate(dockerCli *command.DockerCli, options *updateOptions) error {
var restartPolicy containertypes.RestartPolicy
if options.restartPolicy != "" {
restartPolicy, err = opts.ParseRestartPolicy(options.restartPolicy)
restartPolicy, err = runconfigopts.ParseRestartPolicy(options.restartPolicy)
if err != nil {
return err
}

View File

@ -127,7 +127,7 @@ func legacyWaitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli,
// getExitCode performs an inspect on the container. It returns
// the running state and the exit code.
func getExitCode(ctx context.Context, dockerCli command.Cli, containerID string) (bool, int, error) {
func getExitCode(ctx context.Context, dockerCli *command.DockerCli, containerID string) (bool, int, error) {
c, err := dockerCli.Client().ContainerInspect(ctx, containerID)
if err != nil {
// If we can't connect, then the daemon probably died.

View File

@ -2,16 +2,16 @@ package formatter
import (
"fmt"
"sort"
"strconv"
"strings"
"time"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/go-units"
units "github.com/docker/go-units"
)
const (
@ -171,16 +171,16 @@ func (c *containerContext) Command() string {
}
func (c *containerContext) CreatedAt() string {
return time.Unix(c.c.Created, 0).String()
return time.Unix(int64(c.c.Created), 0).String()
}
func (c *containerContext) RunningFor() string {
createdAt := time.Unix(c.c.Created, 0)
createdAt := time.Unix(int64(c.c.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
}
func (c *containerContext) Ports() string {
return DisplayablePorts(c.c.Ports)
return api.DisplayablePorts(c.c.Ports)
}
func (c *containerContext) Status() string {
@ -257,89 +257,3 @@ func (c *containerContext) Networks() string {
return strings.Join(networks, ",")
}
// DisplayablePorts returns formatted string representing open ports of container
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
// it's used by command 'docker ps'
func DisplayablePorts(ports []types.Port) string {
type portGroup struct {
first uint16
last uint16
}
groupMap := make(map[string]*portGroup)
var result []string
var hostMappings []string
var groupMapKeys []string
sort.Sort(byPortInfo(ports))
for _, port := range ports {
current := port.PrivatePort
portKey := port.Type
if port.IP != "" {
if port.PublicPort != current {
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
continue
}
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
}
group := groupMap[portKey]
if group == nil {
groupMap[portKey] = &portGroup{first: current, last: current}
// record order that groupMap keys are created
groupMapKeys = append(groupMapKeys, portKey)
continue
}
if current == (group.last + 1) {
group.last = current
continue
}
result = append(result, formGroup(portKey, group.first, group.last))
groupMap[portKey] = &portGroup{first: current, last: current}
}
for _, portKey := range groupMapKeys {
g := groupMap[portKey]
result = append(result, formGroup(portKey, g.first, g.last))
}
result = append(result, hostMappings...)
return strings.Join(result, ", ")
}
func formGroup(key string, start, last uint16) string {
parts := strings.Split(key, "/")
groupType := parts[0]
var ip string
if len(parts) > 1 {
ip = parts[0]
groupType = parts[1]
}
group := strconv.Itoa(int(start))
if start != last {
group = fmt.Sprintf("%s-%d", group, last)
}
if ip != "" {
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
}
return fmt.Sprintf("%s/%s", group, groupType)
}
// byPortInfo is a temporary type used to sort types.Port by its fields
type byPortInfo []types.Port
func (r byPortInfo) Len() int { return len(r) }
func (r byPortInfo) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r byPortInfo) Less(i, j int) bool {
if r[i].PrivatePort != r[j].PrivatePort {
return r[i].PrivatePort < r[j].PrivatePort
}
if r[i].IP != r[j].IP {
return r[i].IP < r[j].IP
}
if r[i].PublicPort != r[j].PublicPort {
return r[i].PublicPort < r[j].PublicPort
}
return r[i].Type < r[j].Type
}

View File

@ -11,7 +11,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestContainerPsContext(t *testing.T) {
@ -227,7 +226,7 @@ size: 0B
Context{Format: NewContainerFormat("{{.Image}}", false, true)},
"ubuntu\nubuntu\n",
},
// Special headers for customized table format
// Special headers for customerized table format
{
Context{Format: NewContainerFormat(`table {{truncate .ID 5}}\t{{json .Image}} {{.RunningFor}}/{{title .Status}}/{{pad .Ports 2 2}}.{{upper .Names}} {{lower .Status}}`, false, true)},
`CONTAINER ID IMAGE CREATED/STATUS/ PORTS .NAMES STATUS
@ -358,11 +357,12 @@ func TestContainerContextWriteJSON(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var m map[string]interface{}
err := json.Unmarshal([]byte(line), &m)
require.NoError(t, err, msg)
assert.Equal(t, expectedJSONs[i], m, msg)
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Equal(t, expectedJSONs[i], m)
}
}
@ -377,11 +377,12 @@ func TestContainerContextWriteJSONField(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var s string
err := json.Unmarshal([]byte(line), &s)
require.NoError(t, err, msg)
assert.Equal(t, containers[i].ID, s, msg)
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Equal(t, containers[i].ID, s)
}
}
@ -410,248 +411,3 @@ func TestContainerBackCompat(t *testing.T) {
buf.Reset()
}
}
type ports struct {
ports []types.Port
expected string
}
// nolint: lll
func TestDisplayablePorts(t *testing.T) {
cases := []ports{
{
[]types.Port{
{
PrivatePort: 9988,
Type: "tcp",
},
},
"9988/tcp"},
{
[]types.Port{
{
PrivatePort: 9988,
Type: "udp",
},
},
"9988/udp",
},
{
[]types.Port{
{
IP: "0.0.0.0",
PrivatePort: 9988,
Type: "tcp",
},
},
"0.0.0.0:0->9988/tcp",
},
{
[]types.Port{
{
PrivatePort: 9988,
PublicPort: 8899,
Type: "tcp",
},
},
"9988/tcp",
},
{
[]types.Port{
{
IP: "4.3.2.1",
PrivatePort: 9988,
PublicPort: 8899,
Type: "tcp",
},
},
"4.3.2.1:8899->9988/tcp",
},
{
[]types.Port{
{
IP: "4.3.2.1",
PrivatePort: 9988,
PublicPort: 9988,
Type: "tcp",
},
},
"4.3.2.1:9988->9988/tcp",
},
{
[]types.Port{
{
PrivatePort: 9988,
Type: "udp",
}, {
PrivatePort: 9988,
Type: "udp",
},
},
"9988/udp, 9988/udp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PublicPort: 9998,
PrivatePort: 9998,
Type: "udp",
}, {
IP: "1.2.3.4",
PublicPort: 9999,
PrivatePort: 9999,
Type: "udp",
},
},
"1.2.3.4:9998-9999->9998-9999/udp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PublicPort: 8887,
PrivatePort: 9998,
Type: "udp",
}, {
IP: "1.2.3.4",
PublicPort: 8888,
PrivatePort: 9999,
Type: "udp",
},
},
"1.2.3.4:8887->9998/udp, 1.2.3.4:8888->9999/udp",
},
{
[]types.Port{
{
PrivatePort: 9998,
Type: "udp",
}, {
PrivatePort: 9999,
Type: "udp",
},
},
"9998-9999/udp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PrivatePort: 6677,
PublicPort: 7766,
Type: "tcp",
}, {
PrivatePort: 9988,
PublicPort: 8899,
Type: "udp",
},
},
"9988/udp, 1.2.3.4:7766->6677/tcp",
},
{
[]types.Port{
{
IP: "1.2.3.4",
PrivatePort: 9988,
PublicPort: 8899,
Type: "udp",
}, {
IP: "1.2.3.4",
PrivatePort: 9988,
PublicPort: 8899,
Type: "tcp",
}, {
IP: "4.3.2.1",
PrivatePort: 2233,
PublicPort: 3322,
Type: "tcp",
},
},
"4.3.2.1:3322->2233/tcp, 1.2.3.4:8899->9988/tcp, 1.2.3.4:8899->9988/udp",
},
{
[]types.Port{
{
PrivatePort: 9988,
PublicPort: 8899,
Type: "udp",
}, {
IP: "1.2.3.4",
PrivatePort: 6677,
PublicPort: 7766,
Type: "tcp",
}, {
IP: "4.3.2.1",
PrivatePort: 2233,
PublicPort: 3322,
Type: "tcp",
},
},
"9988/udp, 4.3.2.1:3322->2233/tcp, 1.2.3.4:7766->6677/tcp",
},
{
[]types.Port{
{
PrivatePort: 80,
Type: "tcp",
}, {
PrivatePort: 1024,
Type: "tcp",
}, {
PrivatePort: 80,
Type: "udp",
}, {
PrivatePort: 1024,
Type: "udp",
}, {
IP: "1.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "tcp",
}, {
IP: "1.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "udp",
}, {
IP: "1.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "tcp",
}, {
IP: "1.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "udp",
}, {
IP: "2.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "tcp",
}, {
IP: "2.1.1.1",
PublicPort: 80,
PrivatePort: 1024,
Type: "udp",
}, {
IP: "2.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "tcp",
}, {
IP: "2.1.1.1",
PublicPort: 1024,
PrivatePort: 80,
Type: "udp",
},
},
"80/tcp, 80/udp, 1024/tcp, 1024/udp, 1.1.1.1:1024->80/tcp, 1.1.1.1:1024->80/udp, 2.1.1.1:1024->80/tcp, 2.1.1.1:1024->80/udp, 1.1.1.1:80->1024/tcp, 1.1.1.1:80->1024/udp, 2.1.1.1:80->1024/tcp, 2.1.1.1:80->1024/udp",
},
}
for _, port := range cases {
actual := DisplayablePorts(port.ports)
assert.Equal(t, port.expected, actual)
}
}

View File

@ -29,12 +29,11 @@ const (
// DiskUsageContext contains disk usage specific information required by the formatter, encapsulate a Context struct.
type DiskUsageContext struct {
Context
Verbose bool
LayersSize int64
Images []*types.ImageSummary
Containers []*types.Container
Volumes []*types.Volume
BuilderSize int64
Verbose bool
LayersSize int64
Images []*types.ImageSummary
Containers []*types.Container
Volumes []*types.Volume
}
func (ctx *DiskUsageContext) startSubsection(format string) (*template.Template, error) {
@ -66,59 +65,49 @@ reclaimable: {{.Reclaimable}}
}
func (ctx *DiskUsageContext) Write() (err error) {
if ctx.Verbose {
return ctx.verboseWrite()
}
ctx.buffer = bytes.NewBufferString("")
ctx.preFormat()
if ctx.Verbose == false {
ctx.buffer = bytes.NewBufferString("")
ctx.preFormat()
tmpl, err := ctx.parseFormat()
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
totalSize: ctx.LayersSize,
images: ctx.Images,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
containers: ctx.Containers,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
volumes: ctx.Volumes,
})
if err != nil {
return err
}
diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
diskUsageContainersCtx.header = map[string]string{
"Type": typeHeader,
"TotalCount": totalHeader,
"Active": activeHeader,
"Size": sizeHeader,
"Reclaimable": reclaimableHeader,
}
ctx.postFormat(tmpl, &diskUsageContainersCtx)
tmpl, err := ctx.parseFormat()
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageImagesContext{
totalSize: ctx.LayersSize,
images: ctx.Images,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageContainersContext{
containers: ctx.Containers,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageVolumesContext{
volumes: ctx.Volumes,
})
if err != nil {
return err
}
err = ctx.contextFormat(tmpl, &diskUsageBuilderContext{
builderSize: ctx.BuilderSize,
})
if err != nil {
return err
}
diskUsageContainersCtx := diskUsageContainersContext{containers: []*types.Container{}}
diskUsageContainersCtx.header = map[string]string{
"Type": typeHeader,
"TotalCount": totalHeader,
"Active": activeHeader,
"Size": sizeHeader,
"Reclaimable": reclaimableHeader,
}
ctx.postFormat(tmpl, &diskUsageContainersCtx)
return err
}
func (ctx *DiskUsageContext) verboseWrite() (err error) {
// First images
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
if err != nil {
@ -187,9 +176,6 @@ func (ctx *DiskUsageContext) verboseWrite() (err error) {
}
}
ctx.postFormat(tmpl, newVolumeContext())
// And build cache
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
return
}
@ -243,11 +229,12 @@ func (c *diskUsageImagesContext) Reclaimable() string {
if c.totalSize > 0 {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/c.totalSize)
}
return units.HumanSize(float64(reclaimable))
return fmt.Sprintf("%s", units.HumanSize(float64(reclaimable)))
}
type diskUsageContainersContext struct {
HeaderContext
verbose bool
containers []*types.Container
}
@ -305,11 +292,12 @@ func (c *diskUsageContainersContext) Reclaimable() string {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
}
return units.HumanSize(float64(reclaimable))
return fmt.Sprintf("%s", units.HumanSize(float64(reclaimable)))
}
type diskUsageVolumesContext struct {
HeaderContext
verbose bool
volumes []*types.Volume
}
@ -366,34 +354,5 @@ func (c *diskUsageVolumesContext) Reclaimable() string {
return fmt.Sprintf("%s (%v%%)", units.HumanSize(float64(reclaimable)), (reclaimable*100)/totalSize)
}
return units.HumanSize(float64(reclaimable))
}
type diskUsageBuilderContext struct {
HeaderContext
builderSize int64
}
func (c *diskUsageBuilderContext) MarshalJSON() ([]byte, error) {
return marshalJSON(c)
}
func (c *diskUsageBuilderContext) Type() string {
return "Build Cache"
}
func (c *diskUsageBuilderContext) TotalCount() string {
return ""
}
func (c *diskUsageBuilderContext) Active() string {
return ""
}
func (c *diskUsageBuilderContext) Size() string {
return units.HumanSize(float64(c.builderSize))
}
func (c *diskUsageBuilderContext) Reclaimable() string {
return c.Size()
return fmt.Sprintf("%s", units.HumanSize(float64(reclaimable)))
}

View File

@ -23,7 +23,6 @@ func TestDiskUsageContextFormatWrite(t *testing.T) {
Images 0 0 0B 0B
Containers 0 0 0B 0B
Local Volumes 0 0 0B 0B
Build Cache 0B 0B
`,
},
{
@ -39,9 +38,6 @@ CONTAINER ID IMAGE COMMAND LOCAL VOLUMES
Local Volumes space usage:
VOLUME NAME LINKS SIZE
Build cache usage: 0B
`,
},
// Errors
@ -74,7 +70,6 @@ Build cache usage: 0B
Images 0 0 0B 0B
Containers 0 0 0B 0B
Local Volumes 0 0 0B 0B
Build Cache 0B 0B
`,
},
{
@ -87,7 +82,6 @@ Build Cache 0B
Images 0
Containers 0
Local Volumes 0
Build Cache
`,
},
// Raw Format
@ -115,12 +109,6 @@ active: 0
size: 0B
reclaimable: 0B
type: Build Cache
total:
active:
size: 0B
reclaimable: 0B
`,
},
}

View File

@ -79,18 +79,21 @@ func (c *historyContext) ID() string {
}
func (c *historyContext) CreatedAt() string {
return units.HumanDuration(time.Now().UTC().Sub(time.Unix(c.h.Created, 0)))
var created string
created = units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(c.h.Created), 0)))
return created
}
func (c *historyContext) CreatedSince() string {
created := units.HumanDuration(time.Now().UTC().Sub(time.Unix(c.h.Created, 0)))
var created string
created = units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(c.h.Created), 0)))
return created + " ago"
}
func (c *historyContext) CreatedBy() string {
createdBy := strings.Replace(c.h.CreatedBy, "\t", " ", -1)
if c.trunc {
return stringutils.Ellipsis(createdBy, 45)
createdBy = stringutils.Ellipsis(createdBy, 45)
}
return createdBy
}

View File

@ -234,12 +234,12 @@ func (c *imageContext) Digest() string {
}
func (c *imageContext) CreatedSince() string {
createdAt := time.Unix(c.i.Created, 0)
createdAt := time.Unix(int64(c.i.Created), 0)
return units.HumanDuration(time.Now().UTC().Sub(createdAt)) + " ago"
}
func (c *imageContext) CreatedAt() string {
return time.Unix(c.i.Created, 0).String()
return time.Unix(int64(c.i.Created), 0).String()
}
func (c *imageContext) Size() string {

View File

@ -3,7 +3,6 @@ package formatter
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
"time"
@ -11,7 +10,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNetworkContext(t *testing.T) {
@ -185,11 +183,12 @@ func TestNetworkContextWriteJSON(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var m map[string]interface{}
err := json.Unmarshal([]byte(line), &m)
require.NoError(t, err, msg)
assert.Equal(t, expectedJSONs[i], m, msg)
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Equal(t, expectedJSONs[i], m)
}
}
@ -204,10 +203,11 @@ func TestNetworkContextWriteJSONField(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var s string
err := json.Unmarshal([]byte(line), &s)
require.NoError(t, err, msg)
assert.Equal(t, networks[i].ID, s, msg)
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Equal(t, networks[i].ID, s)
}
}

View File

@ -3,7 +3,6 @@ package formatter
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
@ -11,7 +10,6 @@ import (
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/pkg/stringid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNodeContext(t *testing.T) {
@ -250,11 +248,12 @@ func TestNodeContextWriteJSON(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var m map[string]interface{}
err := json.Unmarshal([]byte(line), &m)
require.NoError(t, err, msg)
assert.Equal(t, testcase.expected[i], m, msg)
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Equal(t, testcase.expected[i], m)
}
}
}
@ -270,11 +269,12 @@ func TestNodeContextWriteJSONField(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var s string
err := json.Unmarshal([]byte(line), &s)
require.NoError(t, err, msg)
assert.Equal(t, nodes[i].ID, s, msg)
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Equal(t, nodes[i].ID, s)
}
}

View File

@ -12,7 +12,7 @@ func (d *dummy) Func1() string {
return "Func1"
}
func (d *dummy) func2() string { // nolint: unused
func (d *dummy) func2() string {
return "func2(should not be marshalled)"
}

View File

@ -3,13 +3,11 @@ package formatter
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestServiceContextWrite(t *testing.T) {
@ -202,11 +200,12 @@ func TestServiceContextWriteJSON(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var m map[string]interface{}
err := json.Unmarshal([]byte(line), &m)
require.NoError(t, err, msg)
assert.Equal(t, expectedJSONs[i], m, msg)
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Equal(t, expectedJSONs[i], m)
}
}
func TestServiceContextWriteJSONField(t *testing.T) {
@ -230,10 +229,11 @@ func TestServiceContextWriteJSONField(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var s string
err := json.Unmarshal([]byte(line), &s)
require.NoError(t, err, msg)
assert.Equal(t, services[i].Spec.Name, s, msg)
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Equal(t, services[i].Spec.Name, s)
}
}

View File

@ -109,8 +109,10 @@ func NewStatsFormat(source, osType string) Format {
}
// NewContainerStats returns a new ContainerStats entity and sets in it the given name
func NewContainerStats(container string) *ContainerStats {
return &ContainerStats{StatsEntry: StatsEntry{Container: container}}
func NewContainerStats(container, osType string) *ContainerStats {
return &ContainerStats{
StatsEntry: StatsEntry{Container: container},
}
}
// ContainerStatsWrite renders the context for a list of containers statistics
@ -184,7 +186,7 @@ func (c *containerStatsContext) MemUsage() string {
return fmt.Sprintf("-- / --")
}
if c.os == winOSType {
return units.BytesSize(c.s.Memory)
return fmt.Sprintf("%s", units.BytesSize(c.s.Memory))
}
return fmt.Sprintf("%s / %s", units.BytesSize(c.s.Memory), units.BytesSize(c.s.MemoryLimit))
}

View File

@ -3,14 +3,12 @@ package formatter
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/stringid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestVolumeContext(t *testing.T) {
@ -155,11 +153,12 @@ func TestVolumeContextWriteJSON(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var m map[string]interface{}
err := json.Unmarshal([]byte(line), &m)
require.NoError(t, err, msg)
assert.Equal(t, expectedJSONs[i], m, msg)
if err := json.Unmarshal([]byte(line), &m); err != nil {
t.Fatal(err)
}
assert.Equal(t, expectedJSONs[i], m)
}
}
@ -174,10 +173,11 @@ func TestVolumeContextWriteJSONField(t *testing.T) {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
t.Logf("Output: line %d: %s", i, line)
var s string
err := json.Unmarshal([]byte(line), &s)
require.NoError(t, err, msg)
assert.Equal(t, volumes[i].Name, s, msg)
if err := json.Unmarshal([]byte(line), &s); err != nil {
t.Fatal(err)
}
assert.Equal(t, volumes[i].Name, s)
}
}

View File

@ -12,7 +12,6 @@ import (
"regexp"
"runtime"
"github.com/Sirupsen/logrus"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image/build"
@ -26,6 +25,7 @@ import (
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/urlutil"
runconfigopts "github.com/docker/docker/runconfig/opts"
units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/spf13/cobra"
@ -62,7 +62,6 @@ type buildOptions struct {
squash bool
target string
imageIDFile string
stream bool
}
// dockerfileFromStdin returns true when the user specified that the Dockerfile
@ -77,20 +76,16 @@ func (o buildOptions) contextFromStdin() bool {
return o.context == "-"
}
func newBuildOptions() buildOptions {
// NewBuildCommand creates a new `docker build` command
func NewBuildCommand(dockerCli *command.DockerCli) *cobra.Command {
ulimits := make(map[string]*units.Ulimit)
return buildOptions{
options := buildOptions{
tags: opts.NewListOpts(validateTag),
buildArgs: opts.NewListOpts(opts.ValidateEnv),
ulimits: opts.NewUlimitOpt(&ulimits),
labels: opts.NewListOpts(opts.ValidateEnv),
extraHosts: opts.NewListOpts(opts.ValidateExtraHost),
}
}
// NewBuildCommand creates a new `docker build` command
func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
options := newBuildOptions()
cmd := &cobra.Command{
Use: "build [OPTIONS] PATH | URL | -",
@ -139,10 +134,6 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
flags.SetAnnotation("squash", "experimental", nil)
flags.SetAnnotation("squash", "version", []string{"1.25"})
flags.BoolVar(&options.stream, "stream", false, "Stream attaches to server to negotiate build context")
flags.SetAnnotation("stream", "experimental", nil)
flags.SetAnnotation("stream", "version", []string{"1.31"})
return cmd
}
@ -163,7 +154,7 @@ func (out *lastProgressOutput) WriteProgress(prog progress.Progress) error {
}
// nolint: gocyclo
func runBuild(dockerCli command.Cli, options buildOptions) error {
func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
var (
buildCtx io.ReadCloser
dockerfileCtx io.ReadCloser
@ -173,7 +164,6 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
relDockerfile string
progBuff io.Writer
buildBuff io.Writer
remote string
)
if options.dockerfileFromStdin() {
@ -199,7 +189,6 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
switch {
case options.contextFromStdin():
// buildCtx is tar archive. if stdin was dockerfile then it is wrapped
buildCtx, relDockerfile, err = build.GetContextFromReader(dockerCli.In(), options.dockerfileName)
case isLocalDir(specifiedContext):
contextDir, relDockerfile, err = build.GetContextFromLocalDir(specifiedContext, options.dockerfileName)
@ -223,8 +212,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
contextDir = tempDir
}
// read from a directory into tar archive
if buildCtx == nil && !options.stream {
if buildCtx == nil {
excludes, err := build.ReadDockerignore(contextDir)
if err != nil {
return err
@ -241,7 +229,13 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
}
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
compression := archive.Uncompressed
if options.compress {
compression = archive.Gzip
}
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
Compression: compression,
ExcludePatterns: excludes,
})
if err != nil {
@ -249,52 +243,24 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
}
}
// replace Dockerfile if it was added from stdin and there is archive context
if dockerfileCtx != nil && buildCtx != nil {
// replace Dockerfile if added dynamically
if dockerfileCtx != nil {
buildCtx, relDockerfile, err = build.AddDockerfileToBuildContext(dockerfileCtx, buildCtx)
if err != nil {
return err
}
}
// if streaming and dockerfile was not from stdin then read from file
// to the same reader that is usually stdin
if options.stream && dockerfileCtx == nil {
dockerfileCtx, err = os.Open(relDockerfile)
if err != nil {
return errors.Wrapf(err, "failed to open %s", relDockerfile)
}
defer dockerfileCtx.Close()
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := context.Background()
var resolvedTags []*resolvedTag
if command.IsTrusted() {
translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
return TrustedReference(ctx, dockerCli, ref, nil)
}
// if there is a tar wrapper, the dockerfile needs to be replaced inside it
if buildCtx != nil {
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls.
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags)
} else if dockerfileCtx != nil {
// if there was not archive context still do the possible replacements in Dockerfile
newDockerfile, _, err := rewriteDockerfileFrom(ctx, dockerfileCtx, translator)
if err != nil {
return err
}
dockerfileCtx = ioutil.NopCloser(bytes.NewBuffer(newDockerfile))
}
}
if options.compress {
buildCtx, err = build.Compress(buildCtx)
if err != nil {
return err
}
// Wrap the tar archive to replace the Dockerfile entry with the rewritten
// Dockerfile which uses trusted pulls.
buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags)
}
// Setup an upload progress bar
@ -303,46 +269,9 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
progressOutput = &lastProgressOutput{output: progressOutput}
}
// if up to this point nothing has set the context then we must have have
// another way for sending it(streaming) and set the context to the Dockerfile
if dockerfileCtx != nil && buildCtx == nil {
buildCtx = dockerfileCtx
}
var body io.Reader = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
s, err := trySession(dockerCli, contextDir)
if err != nil {
return err
}
var body io.Reader
if buildCtx != nil && !options.stream {
body = progress.NewProgressReader(buildCtx, progressOutput, 0, "", "Sending build context to Docker daemon")
}
// add context stream to the session
if options.stream && s != nil {
syncDone := make(chan error) // used to signal first progress reporting completed.
// progress would also send errors but don't need it here as errors
// are handled by session.Run() and ImageBuild()
if err := addDirToSession(s, contextDir, progressOutput, syncDone); err != nil {
return err
}
buf := newBufferedWriter(syncDone, buildBuff)
defer func() {
select {
case <-buf.flushed:
case <-ctx.Done():
}
}()
buildBuff = buf
remote = clientSessionRemote
body = buildCtx
}
configFile := dockerCli.ConfigFile()
authConfigs, _ := configFile.GetAllCredentials()
authConfigs, _ := dockerCli.GetAllCredentials()
buildOptions := types.ImageBuildOptions{
Memory: options.memory.Value(),
MemorySwap: options.memorySwap.Value(),
@ -362,27 +291,15 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
Dockerfile: relDockerfile,
ShmSize: options.shmSize.Value(),
Ulimits: options.ulimits.GetList(),
BuildArgs: configFile.ParseProxyConfig(dockerCli.Client().DaemonHost(), options.buildArgs.GetAll()),
BuildArgs: runconfigopts.ConvertKVStringsToMapWithNil(options.buildArgs.GetAll()),
AuthConfigs: authConfigs,
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
CacheFrom: options.cacheFrom,
SecurityOpt: options.securityOpt,
NetworkMode: options.networkMode,
Squash: options.squash,
ExtraHosts: options.extraHosts.GetAll(),
Target: options.target,
RemoteContext: remote,
}
if s != nil {
go func() {
logrus.Debugf("running session: %v", s.UUID())
if err := s.Run(ctx, dockerCli.Client().DialSession); err != nil {
logrus.Error(err)
cancel() // cancel progress context
}
}()
buildOptions.SessionID = s.UUID()
}
response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
@ -390,7 +307,6 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
if options.quiet {
fmt.Fprintf(dockerCli.Err(), "%s", progBuff)
}
cancel()
return err
}
defer response.Body.Close()

View File

@ -1,25 +1,25 @@
package build
import (
"archive/tar"
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"archive/tar"
"bytes"
"time"
"github.com/docker/docker/builder/remotecontext/git"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/gitutils"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/streamformatter"
"github.com/docker/docker/pkg/stringid"
@ -29,8 +29,6 @@ import (
const (
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
DefaultDockerfileName string = "Dockerfile"
// archiveHeaderSize is the number of bytes in an archive header
archiveHeaderSize = 512
)
// ValidateContextDirectory checks if all the contents of the directory
@ -87,12 +85,12 @@ func ValidateContextDirectory(srcPath string, excludes []string) error {
func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCloser, relDockerfile string, err error) {
buf := bufio.NewReader(r)
magic, err := buf.Peek(archiveHeaderSize)
magic, err := buf.Peek(archive.HeaderSize)
if err != nil && err != io.EOF {
return nil, "", errors.Errorf("failed to peek context header from STDIN: %v", err)
}
if IsArchive(magic) {
if archive.IsArchive(magic) {
return ioutils.NewReadCloserWrapper(buf, func() error { return r.Close() }), dockerfileName, nil
}
@ -136,18 +134,6 @@ func GetContextFromReader(r io.ReadCloser, dockerfileName string) (out io.ReadCl
}
// IsArchive checks for the magic bytes of a tar or any supported compression
// algorithm.
func IsArchive(header []byte) bool {
compression := archive.DetectCompression(header)
if compression != archive.Uncompressed {
return true
}
r := tar.NewReader(bytes.NewBuffer(header))
_, err := r.Next()
return err == nil
}
// GetContextFromGitURL uses a Git URL as context for a `docker build`. The
// git repo is cloned into a temporary directory used as the context directory.
// Returns the absolute path to the temporary context directory, the relative
@ -157,7 +143,7 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error)
if _, err := exec.LookPath("git"); err != nil {
return "", "", errors.Wrapf(err, "unable to find 'git'")
}
absContextDir, err := git.Clone(gitURL)
absContextDir, err := gitutils.Clone(gitURL)
if err != nil {
return "", "", errors.Wrapf(err, "unable to 'git clone' to temporary context directory")
}
@ -175,7 +161,7 @@ func GetContextFromGitURL(gitURL, dockerfileName string) (string, string, error)
// Returns the tar archive used for the context and a path of the
// dockerfile inside the tar.
func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.ReadCloser, string, error) {
response, err := getWithStatusError(remoteURL)
response, err := httputils.Download(remoteURL)
if err != nil {
return nil, "", errors.Errorf("unable to download remote context %s: %v", remoteURL, err)
}
@ -187,24 +173,6 @@ func GetContextFromURL(out io.Writer, remoteURL, dockerfileName string) (io.Read
return GetContextFromReader(ioutils.NewReadCloserWrapper(progReader, func() error { return response.Body.Close() }), dockerfileName)
}
// getWithStatusError does an http.Get() and returns an error if the
// status code is 4xx or 5xx.
func getWithStatusError(url string) (resp *http.Response, err error) {
if resp, err = http.Get(url); err != nil {
return nil, err
}
if resp.StatusCode < 400 {
return resp, nil
}
msg := fmt.Sprintf("failed to GET %s with status %s", url, resp.Status)
body, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
return nil, errors.Wrapf(err, msg+": error reading body")
}
return nil, errors.Errorf(msg+": %s", bytes.TrimSpace(body))
}
// GetContextFromLocalDir uses the given local directory as context for a
// `docker build`. Returns the absolute path to the local context directory,
// the relative path of the dockerfile in that context directory, and a non-nil
@ -376,27 +344,3 @@ func AddDockerfileToBuildContext(dockerfileCtx io.ReadCloser, buildCtx io.ReadCl
})
return buildCtx, randomName, nil
}
// Compress the build context for sending to the API
func Compress(buildCtx io.ReadCloser) (io.ReadCloser, error) {
pipeReader, pipeWriter := io.Pipe()
go func() {
compressWriter, err := archive.CompressStream(pipeWriter, archive.Gzip)
if err != nil {
pipeWriter.CloseWithError(err)
}
defer buildCtx.Close()
if _, err := pools.Copy(compressWriter, buildCtx); err != nil {
pipeWriter.CloseWithError(
errors.Wrap(err, "failed to compress context"))
compressWriter.Close()
return
}
compressWriter.Close()
pipeWriter.Close()
}()
return pipeReader, nil
}

View File

@ -266,35 +266,3 @@ func chdir(t *testing.T, dir string) func() {
require.NoError(t, os.Chdir(dir))
return func() { require.NoError(t, os.Chdir(workingDirectory)) }
}
func TestIsArchive(t *testing.T) {
var testcases = []struct {
doc string
header []byte
expected bool
}{
{
doc: "nil is not a valid header",
header: nil,
expected: false,
},
{
doc: "invalid header bytes",
header: []byte{0x00, 0x01, 0x02},
expected: false,
},
{
doc: "header for bzip2 archive",
header: []byte{0x42, 0x5A, 0x68},
expected: true,
},
{
doc: "header for 7zip archive is not supported",
header: []byte{0x50, 0x4b, 0x03, 0x04},
expected: false,
},
}
for _, testcase := range testcases {
assert.Equal(t, testcase.expected, IsArchive(testcase.header), testcase.doc)
}
}

View File

@ -1,151 +0,0 @@
package image
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"sync"
"time"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image/build"
cliconfig "github.com/docker/cli/cli/config"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client/session"
"github.com/docker/docker/client/session/filesync"
"github.com/docker/docker/pkg/progress"
"github.com/pkg/errors"
"golang.org/x/time/rate"
)
const clientSessionRemote = "client-session"
func isSessionSupported(dockerCli command.Cli) bool {
return dockerCli.ServerInfo().HasExperimental && versions.GreaterThanOrEqualTo(dockerCli.Client().ClientVersion(), "1.31")
}
func trySession(dockerCli command.Cli, contextDir string) (*session.Session, error) {
var s *session.Session
if isSessionSupported(dockerCli) {
sharedKey, err := getBuildSharedKey(contextDir)
if err != nil {
return nil, errors.Wrap(err, "failed to get build shared key")
}
s, err = session.NewSession(filepath.Base(contextDir), sharedKey)
if err != nil {
return nil, errors.Wrap(err, "failed to create session")
}
}
return s, nil
}
func addDirToSession(session *session.Session, contextDir string, progressOutput progress.Output, done chan error) error {
excludes, err := build.ReadDockerignore(contextDir)
if err != nil {
return err
}
p := &sizeProgress{out: progressOutput, action: "Streaming build context to Docker daemon"}
workdirProvider := filesync.NewFSSyncProvider(contextDir, excludes)
session.Allow(workdirProvider)
// this will be replaced on parallel build jobs. keep the current
// progressbar for now
if snpc, ok := workdirProvider.(interface {
SetNextProgressCallback(func(int, bool), chan error)
}); ok {
snpc.SetNextProgressCallback(p.update, done)
}
return nil
}
type sizeProgress struct {
out progress.Output
action string
limiter *rate.Limiter
}
func (sp *sizeProgress) update(size int, last bool) {
if sp.limiter == nil {
sp.limiter = rate.NewLimiter(rate.Every(100*time.Millisecond), 1)
}
if last || sp.limiter.Allow() {
sp.out.WriteProgress(progress.Progress{Action: sp.action, Current: int64(size), LastUpdate: last})
}
}
type bufferedWriter struct {
done chan error
io.Writer
buf *bytes.Buffer
flushed chan struct{}
mu sync.Mutex
}
func newBufferedWriter(done chan error, w io.Writer) *bufferedWriter {
bw := &bufferedWriter{done: done, Writer: w, buf: new(bytes.Buffer), flushed: make(chan struct{})}
go func() {
<-done
bw.flushBuffer()
}()
return bw
}
func (bw *bufferedWriter) Write(dt []byte) (int, error) {
select {
case <-bw.done:
bw.flushBuffer()
return bw.Writer.Write(dt)
default:
return bw.buf.Write(dt)
}
}
func (bw *bufferedWriter) flushBuffer() {
bw.mu.Lock()
select {
case <-bw.flushed:
default:
bw.Writer.Write(bw.buf.Bytes())
close(bw.flushed)
}
bw.mu.Unlock()
}
func getBuildSharedKey(dir string) (string, error) {
// build session is hash of build dir with node based randomness
s := sha256.Sum256([]byte(fmt.Sprintf("%s:%s", tryNodeIdentifier(), dir)))
return hex.EncodeToString(s[:]), nil
}
func tryNodeIdentifier() (out string) {
out = cliconfig.Dir() // return config dir as default on permission error
if err := os.MkdirAll(cliconfig.Dir(), 0700); err == nil {
sessionFile := filepath.Join(cliconfig.Dir(), ".buildNodeID")
if _, err := os.Lstat(sessionFile); err != nil {
if os.IsNotExist(err) { // create a new file with stored randomness
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return
}
if err := ioutil.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
return
}
}
}
dt, err := ioutil.ReadFile(sessionFile)
if err == nil {
return string(dt)
}
}
return
}

View File

@ -1,70 +0,0 @@
package image
import (
"bytes"
"io"
"io/ioutil"
"os"
"path/filepath"
"sort"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/archive"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
dest, err := ioutil.TempDir("", "test-build-compress-dest")
require.NoError(t, err)
defer os.RemoveAll(dest)
var dockerfileName string
fakeImageBuild := func(_ context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
buffer := new(bytes.Buffer)
tee := io.TeeReader(context, buffer)
assert.NoError(t, archive.Untar(tee, dest, nil))
dockerfileName = options.Dockerfile
header := buffer.Bytes()[:10]
assert.Equal(t, archive.Gzip, archive.DetectCompression(header))
body := new(bytes.Buffer)
return types.ImageBuildResponse{Body: ioutil.NopCloser(body)}, nil
}
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild})
dockerfile := bytes.NewBufferString(`
FROM alpine:3.6
COPY foo /
`)
cli.SetIn(command.NewInStream(ioutil.NopCloser(dockerfile)))
dir, err := ioutil.TempDir("", "test-build-compress")
require.NoError(t, err)
defer os.RemoveAll(dir)
ioutil.WriteFile(filepath.Join(dir, "foo"), []byte("some content"), 0644)
options := newBuildOptions()
options.compress = true
options.dockerfileName = "-"
options.context = dir
err = runBuild(cli, options)
require.NoError(t, err)
files, err := ioutil.ReadDir(dest)
require.NoError(t, err)
actual := []string{}
for _, fileInfo := range files {
actual = append(actual, fileInfo.Name())
}
sort.Strings(actual)
assert.Equal(t, []string{dockerfileName, ".dockerignore", "foo"}, actual)
}

View File

@ -27,7 +27,6 @@ type fakeClient struct {
imageInspectFunc func(image string) (types.ImageInspect, []byte, error)
imageImportFunc func(source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)
imageHistoryFunc func(image string) ([]image.HistoryResponseItem, error)
imageBuildFunc func(context.Context, io.Reader, types.ImageBuildOptions) (types.ImageBuildResponse, error)
}
func (cli *fakeClient) ImageTag(_ context.Context, image, ref string) error {
@ -115,10 +114,3 @@ func (cli *fakeClient) ImageHistory(_ context.Context, img string) ([]image.Hist
}
return []image.HistoryResponseItem{{ID: img, Created: time.Now().Unix()}}, nil
}
func (cli *fakeClient) ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
if cli.imageBuildFunc != nil {
return cli.imageBuildFunc(ctx, context, options)
}
return types.ImageBuildResponse{Body: ioutil.NopCloser(strings.NewReader(""))}, nil
}

View File

@ -8,7 +8,8 @@ import (
)
// NewImageCommand returns a cobra command for `image` subcommands
func NewImageCommand(dockerCli command.Cli) *cobra.Command {
// nolint: interfacer
func NewImageCommand(dockerCli *command.DockerCli) *cobra.Command {
cmd := &cobra.Command{
Use: "image",
Short: "Manage images",

View File

@ -1,6 +1,7 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"regexp"
@ -37,7 +38,8 @@ func TestNewHistoryCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}))
buf := new(bytes.Buffer)
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -88,13 +90,13 @@ func TestNewHistoryCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc})
cmd := NewHistoryCommand(cli)
buf := new(bytes.Buffer)
cmd := NewHistoryCommand(test.NewFakeCli(&fakeClient{imageHistoryFunc: tc.imageHistoryFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := cli.OutBuffer().String()
actual := buf.String()
if tc.outputRegex == "" {
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("history-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)

View File

@ -37,7 +37,7 @@ func TestNewImportCommandErrors(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewImportCommand(test.NewFakeCliWithOutput(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -45,7 +45,7 @@ func TestNewImportCommandErrors(t *testing.T) {
}
func TestNewImportCommandInvalidFile(t *testing.T) {
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{}))
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"testdata/import-command-success.unexistent-file"})
testutil.ErrorContains(t, cmd.Execute(), "testdata/import-command-success.unexistent-file")
@ -92,7 +92,7 @@ func TestNewImportCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewImportCommand(test.NewFakeCliWithOutput(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd := NewImportCommand(test.NewFakeCli(&fakeClient{imageImportFunc: tc.imageImportFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())

View File

@ -27,7 +27,7 @@ func TestNewInspectCommandErrors(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(test.NewFakeCliWithOutput(&fakeClient{}, buf))
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -79,7 +79,7 @@ func TestNewInspectCommandSuccess(t *testing.T) {
for _, tc := range testCases {
imageInspectInvocationCount = 0
buf := new(bytes.Buffer)
cmd := newInspectCommand(test.NewFakeCliWithOutput(&fakeClient{imageInspectFunc: tc.imageInspectFunc}, buf))
cmd := newInspectCommand(test.NewFakeCli(&fakeClient{imageInspectFunc: tc.imageInspectFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()

View File

@ -36,7 +36,7 @@ func TestNewImagesCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}))
cmd := NewImagesCommand(test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -81,8 +81,8 @@ func TestNewImagesCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{imageListFunc: tc.imageListFunc}, buf)
cli.SetConfigFile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})
cli := test.NewFakeCli(&fakeClient{imageListFunc: tc.imageListFunc}, buf)
cli.SetConfigfile(&configfile.ConfigFile{ImagesFormat: tc.imageFormat})
cmd := NewImagesCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
@ -95,7 +95,7 @@ func TestNewImagesCommandSuccess(t *testing.T) {
}
func TestNewListCommandAlias(t *testing.T) {
cmd := newListCommand(test.NewFakeCli(&fakeClient{}))
cmd := newListCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
assert.True(t, cmd.HasAlias("images"))
assert.True(t, cmd.HasAlias("list"))
assert.False(t, cmd.HasAlias("other"))

View File

@ -43,7 +43,7 @@ func TestNewLoadCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc})
cli := test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, new(bytes.Buffer))
cli.In().SetIsTerminal(tc.isTerminalIn)
cmd := NewLoadCommand(cli)
cmd.SetOutput(ioutil.Discard)
@ -54,7 +54,7 @@ func TestNewLoadCommandErrors(t *testing.T) {
func TestNewLoadCommandInvalidInput(t *testing.T) {
expectedError := "open *"
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{}))
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"--input", "*"})
err := cmd.Execute()
@ -93,7 +93,7 @@ func TestNewLoadCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewLoadCommand(test.NewFakeCliWithOutput(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, buf))
cmd := NewLoadCommand(test.NewFakeCli(&fakeClient{imageLoadFunc: tc.imageLoadFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()

View File

@ -38,7 +38,7 @@ func TestNewPruneCommandErrors(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(test.NewFakeCliWithOutput(&fakeClient{
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
imagesPruneFunc: tc.imagesPruneFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)
@ -86,7 +86,7 @@ func TestNewPruneCommandSuccess(t *testing.T) {
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := NewPruneCommand(test.NewFakeCliWithOutput(&fakeClient{
cmd := NewPruneCommand(test.NewFakeCli(&fakeClient{
imagesPruneFunc: tc.imagesPruneFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)

View File

@ -1,21 +1,29 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/pkg/testutil/golden"
"github.com/docker/docker/registry"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestNewPullCommandErrors(t *testing.T) {
testCases := []struct {
name string
args []string
expectedError string
name string
args []string
expectedError string
trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named,
authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "wrong-args",
@ -39,8 +47,8 @@ func TestNewPullCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{})
cmd := NewPullCommand(cli)
buf := new(bytes.Buffer)
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -49,8 +57,10 @@ func TestNewPullCommandErrors(t *testing.T) {
func TestNewPullCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
name string
args []string
trustedPullFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named,
authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "simple",
@ -62,13 +72,13 @@ func TestNewPullCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{})
cmd := NewPullCommand(cli)
buf := new(bytes.Buffer)
cmd := NewPullCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
err := cmd.Execute()
assert.NoError(t, err)
actual := cli.OutBuffer().String()
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("pull-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}

View File

@ -1,16 +1,21 @@
package image
import (
"bytes"
"io"
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/pkg/testutil"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestNewPushCommandErrors(t *testing.T) {
@ -45,8 +50,8 @@ func TestNewPushCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc})
cmd := NewPushCommand(cli)
buf := new(bytes.Buffer)
cmd := NewPushCommand(test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -55,8 +60,11 @@ func TestNewPushCommandErrors(t *testing.T) {
func TestNewPushCommandSuccess(t *testing.T) {
testCases := []struct {
name string
args []string
name string
args []string
trustedPushFunc func(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo,
ref reference.Named, authConfig types.AuthConfig,
requestPrivilege types.RequestPrivilegeFunc) error
}{
{
name: "simple",
@ -64,12 +72,12 @@ func TestNewPushCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{
buf := new(bytes.Buffer)
cmd := NewPushCommand(test.NewFakeCli(&fakeClient{
imagePushFunc: func(ref string, options types.ImagePushOptions) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
},
})
cmd := NewPushCommand(cli)
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())

View File

@ -56,8 +56,8 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error
}
var errs []string
for _, img := range images {
dels, err := client.ImageRemove(ctx, img, options)
for _, image := range images {
dels, err := client.ImageRemove(ctx, image, options)
if err != nil {
errs = append(errs, err.Error())
} else {
@ -72,11 +72,7 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error
}
if len(errs) > 0 {
msg := strings.Join(errs, "\n")
if !opts.force {
return errors.New(msg)
}
fmt.Fprintf(dockerCli.Err(), msg)
return errors.Errorf("%s", strings.Join(errs, "\n"))
}
return nil
}

View File

@ -1,6 +1,7 @@
package image
import (
"bytes"
"fmt"
"io/ioutil"
"testing"
@ -14,7 +15,7 @@ import (
)
func TestNewRemoveCommandAlias(t *testing.T) {
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}))
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, new(bytes.Buffer)))
assert.True(t, cmd.HasAlias("rmi"))
assert.True(t, cmd.HasAlias("remove"))
assert.False(t, cmd.HasAlias("other"))
@ -45,7 +46,7 @@ func TestNewRemoveCommandErrors(t *testing.T) {
for _, tc := range testCases {
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
imageRemoveFunc: tc.imageRemoveFunc,
}))
}, new(bytes.Buffer)))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -57,7 +58,6 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
name string
args []string
imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
expectedErrMsg string
}{
{
name: "Image Deleted",
@ -67,15 +67,6 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
return []types.ImageDeleteResponseItem{{Deleted: image}}, nil
},
},
{
name: "Image Deleted with force option",
args: []string{"-f", "image1"},
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
assert.Equal(t, "image1", image)
return []types.ImageDeleteResponseItem{}, errors.Errorf("error removing image")
},
expectedErrMsg: "error removing image",
},
{
name: "Image Untagged",
args: []string{"image1"},
@ -96,15 +87,16 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
fakeCli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc})
cmd := NewRemoveCommand(fakeCli)
buf := new(bytes.Buffer)
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
imageRemoveFunc: tc.imageRemoveFunc,
}, buf))
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs(tc.args)
assert.NoError(t, cmd.Execute())
if tc.expectedErrMsg != "" {
assert.Equal(t, tc.expectedErrMsg, fakeCli.ErrBuffer().String())
}
actual := fakeCli.OutBuffer().String()
err := cmd.Execute()
assert.NoError(t, err)
actual := buf.String()
expected := string(golden.Get(t, []byte(actual), fmt.Sprintf("remove-command-success.%s.golden", tc.name))[:])
testutil.EqualNormalizedString(t, testutil.RemoveSpace, actual, expected)
}

View File

@ -45,7 +45,7 @@ func TestNewSaveCommandErrors(t *testing.T) {
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc})
cli := test.NewFakeCli(&fakeClient{imageSaveFunc: tc.imageSaveFunc}, new(bytes.Buffer))
cli.Out().SetIsTerminal(tc.isTerminal)
cmd := NewSaveCommand(cli)
cmd.SetOutput(ioutil.Discard)
@ -85,7 +85,7 @@ func TestNewSaveCommandSuccess(t *testing.T) {
},
}
for _, tc := range testCases {
cmd := NewSaveCommand(test.NewFakeCliWithOutput(&fakeClient{
cmd := NewSaveCommand(test.NewFakeCli(&fakeClient{
imageSaveFunc: func(images []string) (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("")), nil
},

View File

@ -1,6 +1,7 @@
package image
import (
"bytes"
"io/ioutil"
"testing"
@ -16,8 +17,9 @@ func TestCliNewTagCommandErrors(t *testing.T) {
{"image1", "image2", "image3"},
}
expectedError := "\"tag\" requires exactly 2 argument(s)."
buf := new(bytes.Buffer)
for _, args := range testCases {
cmd := NewTagCommand(test.NewFakeCli(&fakeClient{}))
cmd := NewTagCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetArgs(args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), expectedError)
@ -25,6 +27,7 @@ func TestCliNewTagCommandErrors(t *testing.T) {
}
func TestCliNewTagCommand(t *testing.T) {
buf := new(bytes.Buffer)
cmd := NewTagCommand(
test.NewFakeCli(&fakeClient{
imageTagFunc: func(image string, ref string) error {
@ -32,7 +35,7 @@ func TestCliNewTagCommand(t *testing.T) {
assert.Equal(t, "image2", ref)
return nil
},
}))
}, buf))
cmd.SetArgs([]string{"image1", "image2"})
cmd.SetOutput(ioutil.Discard)
assert.NoError(t, cmd.Execute())

View File

@ -21,9 +21,6 @@
},
"RootFS": {
"Type": ""
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
},
{
@ -48,9 +45,6 @@
},
"RootFS": {
"Type": ""
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]

View File

@ -21,9 +21,6 @@
},
"RootFS": {
"Type": ""
},
"Metadata": {
"LastTagTime": "0001-01-01T00:00:00Z"
}
}
]

View File

@ -1,2 +1,4 @@
Untagged: image1
Deleted: image2
Untagged: image1
Deleted: image2

View File

@ -1 +1,2 @@
Deleted: image1
Deleted: image1

View File

@ -1 +1,2 @@
Untagged: image1
Untagged: image1

View File

@ -110,7 +110,6 @@ func (i *TemplateInspector) tryRawInspectFallback(rawElement []byte) error {
buffer := new(bytes.Buffer)
rdr := bytes.NewReader(rawElement)
dec := json.NewDecoder(rdr)
dec.UseNumber()
if rawErr := dec.Decode(&raw); rawErr != nil {
return errors.Errorf("unable to read inspect data: %v", rawErr)

View File

@ -6,8 +6,6 @@ import (
"testing"
"github.com/docker/docker/pkg/templates"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type testElement struct {
@ -221,39 +219,3 @@ func TestIndentedInspectorRawElements(t *testing.T) {
t.Fatalf("Expected `%s`, got `%s`", expected, b.String())
}
}
// moby/moby#32235
// This test verifies that even if `tryRawInspectFallback` is called the fields containing
// numerical values are displayed correctly.
// For example, `docker inspect --format "{{.Id}} {{.Size}} alpine` and
// `docker inspect --format "{{.ID}} {{.Size}} alpine" will have the same output which is
// sha256:651aa95985aa4a17a38ffcf71f598ec461924ca96865facc2c5782ef2d2be07f 3983636
func TestTemplateInspectorRawFallbackNumber(t *testing.T) {
// Using typedElem to automatically fall to tryRawInspectFallback.
typedElem := struct {
ID string `json:"Id"`
}{"ad3"}
testcases := []struct {
raw []byte
exp string
}{
{raw: []byte(`{"Id": "ad3", "Size": 53317}`), exp: "53317 ad3\n"},
{raw: []byte(`{"Id": "ad3", "Size": 53317.102}`), exp: "53317.102 ad3\n"},
{raw: []byte(`{"Id": "ad3", "Size": 53317.0}`), exp: "53317.0 ad3\n"},
}
b := new(bytes.Buffer)
tmpl, err := templates.Parse("{{.Size}} {{.Id}}")
require.NoError(t, err)
i := NewTemplateInspector(b, tmpl)
for _, tc := range testcases {
err = i.Inspect(typedElem, tc.raw)
require.NoError(t, err)
err = i.Flush()
require.NoError(t, err)
assert.Equal(t, tc.exp, b.String())
b.Reset()
}
}

View File

@ -1,19 +0,0 @@
package network
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
}
func (c *fakeClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) {
if c.networkCreateFunc != nil {
return c.networkCreateFunc(ctx, name, options)
}
return types.NetworkCreateResponse{}, nil
}

View File

@ -8,7 +8,8 @@ import (
)
// NewNetworkCommand returns a cobra command for `network` subcommands
func NewNetworkCommand(dockerCli command.Cli) *cobra.Command {
// nolint: interfacer
func NewNetworkCommand(dockerCli *command.DockerCli) *cobra.Command {
cmd := &cobra.Command{
Use: "network",
Short: "Manage networks",

View File

@ -19,7 +19,7 @@ type connectOptions struct {
linklocalips []string
}
func newConnectCommand(dockerCli command.Cli) *cobra.Command {
func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command {
options := connectOptions{
links: opts.NewListOpts(opts.ValidateLink),
}
@ -45,7 +45,7 @@ func newConnectCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runConnect(dockerCli command.Cli, options connectOptions) error {
func runConnect(dockerCli *command.DockerCli, options connectOptions) error {
client := dockerCli.Client()
epConfig := &network.EndpointSettings{

View File

@ -10,6 +10,7 @@ import (
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
@ -36,7 +37,7 @@ type createOptions struct {
ipamOpt opts.MapOpts
}
func newCreateCommand(dockerCli command.Cli) *cobra.Command {
func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
options := createOptions{
driverOpts: *opts.NewMapOpts(nil, nil),
labels: opts.NewListOpts(opts.ValidateEnv),
@ -82,7 +83,7 @@ func newCreateCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runCreate(dockerCli command.Cli, options createOptions) error {
func runCreate(dockerCli *command.DockerCli, options createOptions) error {
client := dockerCli.Client()
ipamCfg, err := consolidateIpam(options.ipamSubnet, options.ipamIPRange, options.ipamGateway, options.ipamAux.GetAll())
@ -106,7 +107,7 @@ func runCreate(dockerCli command.Cli, options createOptions) error {
Ingress: options.ingress,
Scope: options.scope,
ConfigOnly: options.configOnly,
Labels: opts.ConvertKVStringsToMap(options.labels.GetAll()),
Labels: runconfigopts.ConvertKVStringsToMap(options.labels.GetAll()),
}
if from := options.configFrom; from != "" {
@ -232,13 +233,13 @@ func subnetMatches(subnet, data string) (bool, error) {
_, s, err := net.ParseCIDR(subnet)
if err != nil {
return false, errors.Wrap(err, "invalid subnet")
return false, errors.Errorf("Invalid subnet %s : %v", s, err)
}
if strings.Contains(data, "/") {
ip, _, err = net.ParseCIDR(data)
if err != nil {
return false, err
return false, errors.Errorf("Invalid cidr %s : %v", data, err)
}
} else {
ip = net.ParseIP(data)

View File

@ -1,175 +0,0 @@
package network
import (
"io/ioutil"
"strings"
"testing"
"github.com/docker/cli/cli/internal/test"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/pkg/testutil"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestNetworkCreateErrors(t *testing.T) {
testCases := []struct {
args []string
flags map[string]string
networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
expectedError string
}{
{
expectedError: "exactly 1 argument",
},
{
args: []string{"toto"},
networkCreateFunc: func(ctx context.Context, name string, createBody types.NetworkCreate) (types.NetworkCreateResponse, error) {
return types.NetworkCreateResponse{}, errors.Errorf("error creating network")
},
expectedError: "error creating network",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.0.0/24",
"gateway": "255.0.255.0/24",
"subnet": "10.1.2.0.30.50",
},
expectedError: "invalid CIDR address: 10.1.2.0.30.50",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.0.0.30/24",
"gateway": "255.0.255.0/24",
"subnet": "255.0.0.0/24",
},
expectedError: "invalid CIDR address: 255.255.0.0.30/24",
},
{
args: []string{"toto"},
flags: map[string]string{
"gateway": "255.0.0.0/24",
},
expectedError: "every ip-range or gateway must have a corresponding subnet",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.0.0.0/24",
},
expectedError: "every ip-range or gateway must have a corresponding subnet",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.0.0.0/24",
"gateway": "255.0.0.0/24",
},
expectedError: "every ip-range or gateway must have a corresponding subnet",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.0.0/24",
"gateway": "255.0.255.0/24",
"subnet": "10.1.2.0/23,10.1.3.248/30",
},
expectedError: "multiple overlapping subnet configuration is not supported",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "192.168.1.0/24,192.168.1.200/24",
"gateway": "192.168.1.1,192.168.1.4",
"subnet": "192.168.2.0/24,192.168.1.250/24",
},
expectedError: "cannot configure multiple ranges (192.168.1.200/24, 192.168.1.0/24) on the same subnet (192.168.1.250/24)",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "255.255.200.0/24,255.255.120.0/24",
"gateway": "255.0.255.0/24",
"subnet": "255.255.255.0/24,255.255.0.255/24",
},
expectedError: "no matching subnet for range 255.255.200.0/24",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "192.168.1.0/24",
"gateway": "192.168.1.1,192.168.1.4",
"subnet": "192.168.2.0/24,192.168.1.250/24",
},
expectedError: "cannot configure multiple gateways (192.168.1.4, 192.168.1.1) for the same subnet (192.168.1.250/24)",
},
{
args: []string{"toto"},
flags: map[string]string{
"ip-range": "192.168.1.0/24",
"gateway": "192.168.4.1,192.168.5.4",
"subnet": "192.168.2.0/24,192.168.1.250/24",
},
expectedError: "no matching subnet for gateway 192.168.4.1",
},
{
args: []string{"toto"},
flags: map[string]string{
"gateway": "255.255.0.0/24",
"subnet": "255.255.0.0/24",
"aux-address": "255.255.0.30/24",
},
expectedError: "no matching subnet for aux-address",
},
}
for _, tc := range testCases {
cmd := newCreateCommand(
test.NewFakeCli(&fakeClient{
networkCreateFunc: tc.networkCreateFunc,
}),
)
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
require.NoError(t, cmd.Flags().Set(key, value))
}
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestNetworkCreateWithFlags(t *testing.T) {
expectedDriver := "foo"
expectedOpts := []network.IPAMConfig{
{
"192.168.4.0/24",
"192.168.4.0/24",
"192.168.4.1/24",
map[string]string{},
},
}
cli := test.NewFakeCli(&fakeClient{
networkCreateFunc: func(ctx context.Context, name string, createBody types.NetworkCreate) (types.NetworkCreateResponse, error) {
assert.Equal(t, expectedDriver, createBody.Driver, "not expected driver error")
assert.Equal(t, expectedOpts, createBody.IPAM.Config, "not expected driver error")
return types.NetworkCreateResponse{
ID: name,
}, nil
},
})
args := []string{"banana"}
cmd := newCreateCommand(cli)
cmd.SetArgs(args)
cmd.Flags().Set("driver", "foo")
cmd.Flags().Set("ip-range", "192.168.4.0/24")
cmd.Flags().Set("gateway", "192.168.4.1/24")
cmd.Flags().Set("subnet", "192.168.4.0/24")
assert.NoError(t, cmd.Execute())
assert.Equal(t, "banana", strings.TrimSpace(cli.OutBuffer().String()))
}

View File

@ -14,7 +14,7 @@ type disconnectOptions struct {
force bool
}
func newDisconnectCommand(dockerCli command.Cli) *cobra.Command {
func newDisconnectCommand(dockerCli *command.DockerCli) *cobra.Command {
opts := disconnectOptions{}
cmd := &cobra.Command{
@ -34,7 +34,7 @@ func newDisconnectCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runDisconnect(dockerCli command.Cli, opts disconnectOptions) error {
func runDisconnect(dockerCli *command.DockerCli, opts disconnectOptions) error {
client := dockerCli.Client()
return client.NetworkDisconnect(context.Background(), opts.network, opts.container, opts.force)

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/inspect"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)
@ -16,7 +15,7 @@ type inspectOptions struct {
verbose bool
}
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts inspectOptions
cmd := &cobra.Command{
@ -35,13 +34,13 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
client := dockerCli.Client()
ctx := context.Background()
getNetFunc := func(name string) (interface{}, []byte, error) {
return client.NetworkInspectWithRaw(ctx, name, types.NetworkInspectOptions{Verbose: opts.verbose})
return client.NetworkInspectWithRaw(ctx, name, opts.verbose)
}
return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc)

View File

@ -25,7 +25,7 @@ type listOptions struct {
filter opts.FilterOpt
}
func newListCommand(dockerCli command.Cli) *cobra.Command {
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
options := listOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
@ -47,7 +47,7 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
return cmd
}
func runList(dockerCli command.Cli, options listOptions) error {
func runList(dockerCli *command.DockerCli, options listOptions) error {
client := dockerCli.Client()
listOptions := types.NetworkListOptions{Filters: options.filter.Value()}
networkResources, err := client.NetworkList(context.Background(), listOptions)

View File

@ -7,11 +7,10 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
)
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
return &cobra.Command{
Use: "rm NETWORK [NETWORK...]",
Aliases: []string{"remove"},
@ -28,13 +27,13 @@ const ingressWarning = "WARNING! Before removing the routing-mesh network, " +
"Otherwise, removal may not be effective and functionality of newly create " +
"ingress networks will be impaired.\nAre you sure you want to continue?"
func runRemove(dockerCli command.Cli, networks []string) error {
func runRemove(dockerCli *command.DockerCli, networks []string) error {
client := dockerCli.Client()
ctx := context.Background()
status := 0
for _, name := range networks {
if nw, _, err := client.NetworkInspectWithRaw(ctx, name, types.NetworkInspectOptions{}); err == nil &&
if nw, _, err := client.NetworkInspectWithRaw(ctx, name, false); err == nil &&
nw.Ingress &&
!command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), ingressWarning) {
continue

View File

@ -1,6 +1,7 @@
package node
import (
"bytes"
"io/ioutil"
"testing"
@ -39,11 +40,12 @@ func TestNodeDemoteErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newDemoteCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}))
}, buf))
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
@ -51,6 +53,7 @@ func TestNodeDemoteErrors(t *testing.T) {
}
func TestNodeDemoteNoChange(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newDemoteCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
@ -62,12 +65,13 @@ func TestNodeDemoteNoChange(t *testing.T) {
}
return nil
},
}))
}, buf))
cmd.SetArgs([]string{"nodeID"})
assert.NoError(t, cmd.Execute())
}
func TestNodeDemoteMultipleNode(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newDemoteCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
@ -79,7 +83,7 @@ func TestNodeDemoteMultipleNode(t *testing.T) {
}
return nil
},
}))
}, buf))
cmd.SetArgs([]string{"nodeID1", "nodeID2"})
assert.NoError(t, cmd.Execute())
}

View File

@ -69,7 +69,7 @@ func TestNodeInspectErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
infoFunc: tc.infoFunc,
}, buf))
@ -111,7 +111,7 @@ func TestNodeInspectPretty(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newInspectCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
}, buf))
cmd.SetArgs([]string{"nodeID"})

View File

@ -1,27 +1,15 @@
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
@ -80,6 +68,5 @@ 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)
}

View File

@ -9,8 +9,6 @@ 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"
@ -44,10 +42,12 @@ func TestNodeListErrorOnAPIFailure(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: tc.nodeListFunc,
infoFunc: tc.infoFunc,
})
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
cmd.SetOutput(ioutil.Discard)
assert.EqualError(t, cmd.Execute(), tc.expectedError)
@ -55,6 +55,7 @@ func TestNodeListErrorOnAPIFailure(t *testing.T) {
}
func TestNodeList(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
@ -70,25 +71,25 @@ func TestNodeList(t *testing.T) {
},
}, nil
},
})
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
assert.NoError(t, cmd.Execute())
out := cli.OutBuffer().String()
assert.Contains(t, out, `nodeID1 * nodeHostname1 Ready Active Leader`)
assert.Contains(t, out, `nodeID2 nodeHostname2 Ready Active Reachable`)
assert.Contains(t, out, `nodeID3 nodeHostname3 Ready Active`)
assert.Contains(t, buf.String(), `nodeID1 * nodeHostname1 Ready Active Leader`)
assert.Contains(t, buf.String(), `nodeID2 nodeHostname2 Ready Active Reachable`)
assert.Contains(t, buf.String(), `nodeID3 nodeHostname3 Ready Active`)
}
func TestNodeListQuietShouldOnlyPrintIDs(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
*Node(),
}, nil
},
}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
cmd.Flags().Set("quiet", "true")
assert.NoError(t, cmd.Execute())
@ -98,7 +99,8 @@ func TestNodeListQuietShouldOnlyPrintIDs(t *testing.T) {
// Test case for #24090
func TestNodeListContainsHostname(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{}, buf)
cli := test.NewFakeCli(&fakeClient{}, buf)
cli.SetConfigfile(&configfile.ConfigFile{})
cmd := newListCommand(cli)
assert.NoError(t, cmd.Execute())
assert.Contains(t, buf.String(), "HOSTNAME")
@ -106,7 +108,7 @@ func TestNodeListContainsHostname(t *testing.T) {
func TestNodeListDefaultFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
*Node(NodeID("nodeID1"), Hostname("nodeHostname1"), Manager(Leader())),
@ -122,7 +124,7 @@ func TestNodeListDefaultFormat(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigFile(&configfile.ConfigFile{
cli.SetConfigfile(&configfile.ConfigFile{
NodesFormat: "{{.ID}}: {{.Hostname}} {{.Status}}/{{.ManagerStatus}}",
})
cmd := newListCommand(cli)
@ -134,7 +136,7 @@ func TestNodeListDefaultFormat(t *testing.T) {
func TestNodeListFormat(t *testing.T) {
buf := new(bytes.Buffer)
cli := test.NewFakeCliWithOutput(&fakeClient{
cli := test.NewFakeCli(&fakeClient{
nodeListFunc: func() ([]swarm.Node, error) {
return []swarm.Node{
*Node(NodeID("nodeID1"), Hostname("nodeHostname1"), Manager(Leader())),
@ -149,7 +151,7 @@ func TestNodeListFormat(t *testing.T) {
}, nil
},
}, buf)
cli.SetConfigFile(&configfile.ConfigFile{
cli.SetConfigfile(&configfile.ConfigFile{
NodesFormat: "{{.ID}}: {{.Hostname}} {{.Status}}/{{.ManagerStatus}}",
})
cmd := newListCommand(cli)
@ -158,22 +160,3 @@ 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))
}

View File

@ -11,6 +11,7 @@ type nodeOptions struct {
}
type annotations struct {
name string
labels opts.ListOpts
}

View File

@ -42,7 +42,7 @@ func TestNodePromoteErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newPromoteCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}, buf))
@ -55,7 +55,7 @@ func TestNodePromoteErrors(t *testing.T) {
func TestNodePromoteNoChange(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newPromoteCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
return *Node(Manager()), []byte{}, nil
},
@ -73,7 +73,7 @@ func TestNodePromoteNoChange(t *testing.T) {
func TestNodePromoteMultipleNode(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newPromoteCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeInspectFunc: func() (swarm.Node, []byte, error) {
return *Node(), []byte{}, nil
},

View File

@ -5,6 +5,7 @@ import (
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/task"
"github.com/docker/cli/opts"
@ -87,7 +88,11 @@ func runPs(dockerCli command.Cli, options psOptions) error {
format := options.format
if len(format) == 0 {
format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet)
if dockerCli.ConfigFile() != nil && len(dockerCli.ConfigFile().TasksFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().TasksFormat
} else {
format = formatter.TableFormatKey
}
}
if len(errs) == 0 || len(tasks) != 0 {

View File

@ -52,7 +52,7 @@ func TestNodePsErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newPsCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
infoFunc: tc.infoFunc,
nodeInspectFunc: tc.nodeInspectFunc,
taskInspectFunc: tc.taskInspectFunc,
@ -103,11 +103,11 @@ func TestNodePs(t *testing.T) {
},
taskListFunc: func(options types.TaskListOptions) ([]swarm.Task, error) {
return []swarm.Task{
*Task(TaskID("taskID1"), TaskServiceID("failure"),
*Task(TaskID("taskID1"), ServiceID("failure"),
WithStatus(Timestamp(time.Now().Add(-2*time.Hour)), StatusErr("a task error"))),
*Task(TaskID("taskID2"), TaskServiceID("failure"),
*Task(TaskID("taskID2"), ServiceID("failure"),
WithStatus(Timestamp(time.Now().Add(-3*time.Hour)), StatusErr("a task error"))),
*Task(TaskID("taskID3"), TaskServiceID("failure"),
*Task(TaskID("taskID3"), ServiceID("failure"),
WithStatus(Timestamp(time.Now().Add(-4*time.Hour)), StatusErr("a task error"))),
}, nil
},
@ -116,7 +116,7 @@ func TestNodePs(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newPsCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
infoFunc: tc.infoFunc,
nodeInspectFunc: tc.nodeInspectFunc,
taskInspectFunc: tc.taskInspectFunc,

View File

@ -31,7 +31,7 @@ func TestNodeRemoveErrors(t *testing.T) {
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newRemoveCommand(
test.NewFakeCliWithOutput(&fakeClient{
test.NewFakeCli(&fakeClient{
nodeRemoveFunc: tc.nodeRemoveFunc,
}, buf))
cmd.SetArgs(tc.args)
@ -42,7 +42,7 @@ func TestNodeRemoveErrors(t *testing.T) {
func TestNodeRemoveMultiple(t *testing.T) {
buf := new(bytes.Buffer)
cmd := newRemoveCommand(test.NewFakeCliWithOutput(&fakeClient{}, buf))
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}, buf))
cmd.SetArgs([]string{"nodeID1", "nodeID2"})
assert.NoError(t, cmd.Execute())
}

View File

@ -1,3 +0,0 @@
node-1-foo:
node-2-foo: Leader
node-10-foo: Reachable

View File

@ -7,6 +7,7 @@ import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types/swarm"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -94,7 +95,7 @@ func mergeNodeUpdate(flags *pflag.FlagSet) func(*swarm.Node) error {
}
if flags.Changed(flagLabelAdd) {
labels := flags.Lookup(flagLabelAdd).Value.(*opts.ListOpts).GetAll()
for k, v := range opts.ConvertKVStringsToMap(labels) {
for k, v := range runconfigopts.ConvertKVStringsToMap(labels) {
spec.Annotations.Labels[k] = v
}
}

View File

@ -1,6 +1,7 @@
package node
import (
"bytes"
"io/ioutil"
"testing"
@ -56,11 +57,12 @@ func TestNodeUpdateErrors(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newUpdateCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}))
}, buf))
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)
@ -156,11 +158,12 @@ func TestNodeUpdate(t *testing.T) {
},
}
for _, tc := range testCases {
buf := new(bytes.Buffer)
cmd := newUpdateCommand(
test.NewFakeCli(&fakeClient{
nodeInspectFunc: tc.nodeInspectFunc,
nodeUpdateFunc: tc.nodeUpdateFunc,
}))
}, buf))
cmd.SetArgs(tc.args)
for key, value := range tc.flags {
cmd.Flags().Set(key, value)

View File

@ -0,0 +1,51 @@
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"
"github.com/spf13/cobra"
)
// NewContainerPruneCommand returns a cobra prune command for containers
func NewContainerPruneCommand(dockerCli command.Cli) *cobra.Command {
return container.NewPruneCommand(dockerCli)
}
// NewVolumePruneCommand returns a cobra prune command for volumes
func NewVolumePruneCommand(dockerCli command.Cli) *cobra.Command {
return volume.NewPruneCommand(dockerCli)
}
// NewImagePruneCommand returns a cobra prune command for images
func NewImagePruneCommand(dockerCli command.Cli) *cobra.Command {
return image.NewPruneCommand(dockerCli)
}
// NewNetworkPruneCommand returns a cobra prune command for Networks
func NewNetworkPruneCommand(dockerCli command.Cli) *cobra.Command {
return network.NewPruneCommand(dockerCli)
}
// 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)
}

View File

@ -70,7 +70,7 @@ func ResolveAuthConfig(ctx context.Context, cli Cli, index *registrytypes.IndexI
configKey = ElectAuthServer(ctx, cli)
}
a, _ := cli.ConfigFile().GetAuthConfig(configKey)
a, _ := cli.CredentialsStore(configKey).Get(configKey)
return a
}
@ -85,7 +85,7 @@ func ConfigureAuth(cli Cli, flUser, flPassword, serverAddress string, isDefaultR
serverAddress = registry.ConvertToHostname(serverAddress)
}
authconfig, err := cli.ConfigFile().GetAuthConfig(serverAddress)
authconfig, err := cli.CredentialsStore(serverAddress).Get(serverAddress)
if err != nil {
return authconfig, err
}

View File

@ -2,8 +2,6 @@ package registry
import (
"fmt"
"io/ioutil"
"strings"
"golang.org/x/net/context"
@ -18,7 +16,7 @@ type loginOptions struct {
serverAddress string
user string
password string
passwordStdin bool
email string
}
// NewLoginCommand creates a new `docker login` command
@ -42,7 +40,6 @@ func NewLoginCommand(dockerCli command.Cli) *cobra.Command {
flags.StringVarP(&opts.user, "username", "u", "", "Username")
flags.StringVarP(&opts.password, "password", "p", "", "Password")
flags.BoolVarP(&opts.passwordStdin, "password-stdin", "", false, "Take the password from stdin")
return cmd
}
@ -51,27 +48,6 @@ func runLogin(dockerCli command.Cli, opts loginOptions) error {
ctx := context.Background()
clnt := dockerCli.Client()
if opts.password != "" {
fmt.Fprintln(dockerCli.Err(), "WARNING! Using --password via the CLI is insecure. Use --password-stdin.")
if opts.passwordStdin {
return errors.New("--password and --password-stdin are mutually exclusive")
}
}
if opts.passwordStdin {
if opts.user == "" {
return errors.New("Must provide --username with --password-stdin")
}
contents, err := ioutil.ReadAll(dockerCli.In())
if err != nil {
return err
}
opts.password = strings.TrimSuffix(string(contents), "\n")
opts.password = strings.TrimSuffix(opts.password, "\r")
}
var (
serverAddress string
authServer = command.ElectAuthServer(ctx, dockerCli)
@ -96,7 +72,7 @@ func runLogin(dockerCli command.Cli, opts loginOptions) error {
authConfig.Password = ""
authConfig.IdentityToken = response.IdentityToken
}
if err := dockerCli.ConfigFile().GetCredentialsStore(serverAddress).Store(authConfig); err != nil {
if err := dockerCli.CredentialsStore(serverAddress).Store(authConfig); err != nil {
return errors.Errorf("Error saving credentials: %v", err)
}

View File

@ -68,7 +68,7 @@ func runLogout(dockerCli command.Cli, serverAddress string) error {
fmt.Fprintf(dockerCli.Out(), "Removing login credentials for %s\n", hostnameAddress)
for _, r := range regsToLogout {
if err := dockerCli.ConfigFile().GetCredentialsStore(r).Erase(r); err != nil {
if err := dockerCli.CredentialsStore(r).Erase(r); err != nil {
fmt.Fprintf(dockerCli.Err(), "WARNING: could not erase credentials: %v\n", err)
}
}

Some files were not shown because too many files have changed in this diff Show More