Browse code

Merge pull request #29002 from vieux/1.13.0-rc3-cherrypicks

1.13.0-rc3 cherry-picks: part2

Victor Vieux authored on 2016/12/03 09:51:49
Showing 96 changed files
... ...
@@ -99,7 +99,6 @@ be found.
99 99
 + Add `Isolation` to the /info endpoint [#26255](https://github.com/docker/docker/pull/26255)
100 100
 + Add `userns` to the /info endpoint [#27840](https://github.com/docker/docker/pull/27840)
101 101
 - Do not allow more than one mode be requested at once in the services endpoint [#26643](https://github.com/docker/docker/pull/26643)
102
-+ Add `--mount` flag to `docker create` and `docker run` [#26825](https://github.com/docker/docker/pull/26825)[#28150](https://github.com/docker/docker/pull/28150)
103 102
 + Add capability to /containers/create API to specify mounts in a more granular and safer way [#22373](https://github.com/docker/docker/pull/22373)
104 103
 + Add `--format` flag to `network ls` and `volume ls` [#23475](https://github.com/docker/docker/pull/23475)
105 104
 * Allow the top-level `docker inspect` command to inspect any kind of resource [#23614](https://github.com/docker/docker/pull/23614)
... ...
@@ -18,6 +18,10 @@
18 18
 # ppc64le/golang is a debian:jessie based image with golang installed
19 19
 FROM ppc64le/golang:1.6.3
20 20
 
21
+# allow replacing httpredir or deb mirror
22
+ARG APT_MIRROR=deb.debian.org
23
+RUN sed -ri "s/(httpredir|deb).debian.org/$APT_MIRROR/g" /etc/apt/sources.list
24
+
21 25
 # Packaged dependencies
22 26
 RUN apt-get update && apt-get install -y \
23 27
 	apparmor \
... ...
@@ -144,4 +144,4 @@ swagger-gen:
144 144
 		-w /go/src/github.com/docker/docker \
145 145
 		--entrypoint hack/generate-swagger-api.sh \
146 146
 		-e GOPATH=/go \
147
-		quay.io/goswagger/swagger
147
+		quay.io/goswagger/swagger:0.7.4
... ...
@@ -383,6 +383,7 @@ func (sr *swarmRouter) removeSecret(ctx context.Context, w http.ResponseWriter,
383 383
 	if err := sr.backend.RemoveSecret(vars["id"]); err != nil {
384 384
 		return err
385 385
 	}
386
+	w.WriteHeader(http.StatusNoContent)
386 387
 
387 388
 	return nil
388 389
 }
... ...
@@ -1,3 +1,14 @@
1
+# A Swagger 2.0 (a.k.a. OpenAPI) definition of the Engine API.
2
+#
3
+# This is used for generating API documentation and the types used by the
4
+# client/server. See api/README.md for more information.
5
+#
6
+# Some style notes:
7
+# - This file is used by ReDoc, which allows GitHub Flavored Markdown in
8
+#   descriptions.
9
+# - There is no maximum line length, for ease of editing and pretty diffs.
10
+# - operationIds are in the format "NounVerb", with a singular noun.
11
+
1 12
 swagger: "2.0"
2 13
 schemes:
3 14
   - "http"
... ...
@@ -45,13 +56,13 @@ info:
45 45
 
46 46
     Docker version  | API version | Changes
47 47
     ----------------|-------------|---------
48
-    1.12.x | [1.24](/engine/api/v1.24/) | [API changes](/engine/api/version-history/#v1-24-api-changes)
49
-    1.11.x | [1.23](/engine/api/v1.23/) | [API changes](/engine/api/version-history/#v1-23-api-changes)
50
-    1.10.x | [1.22](/engine/api/v1.22/) | [API changes](/engine/api/version-history/#v1-22-api-changes)
51
-    1.9.x | [1.21](/engine/api/v1.21/) | [API changes](/engine/api/version-history/#v1-21-api-changes)
52
-    1.8.x | [1.20](/engine/api/v1.20/) | [API changes](/engine/api/version-history/#v1-20-api-changes)
53
-    1.7.x | [1.19](/engine/api/v1.19/) | [API changes](/engine/api/version-history/#v1-19-api-changes)
54
-    1.6.x | [1.18](/engine/api/v1.18/) | [API changes](/engine/api/version-history/#v1-18-api-changes)
48
+    1.12.x | [1.24](https://docs.docker.com/engine/api/v1.24/) | [API changes](https://docs.docker.com/engine/api/version-history/#v1-24-api-changes)
49
+    1.11.x | [1.23](https://docs.docker.com/engine/api/v1.23/) | [API changes](https://docs.docker.com/engine/api/version-history/#v1-23-api-changes)
50
+    1.10.x | [1.22](https://docs.docker.com/engine/api/v1.22/) | [API changes](https://docs.docker.com/engine/api/version-history/#v1-22-api-changes)
51
+    1.9.x | [1.21](https://docs.docker.com/engine/api/v1.21/) | [API changes](https://docs.docker.com/engine/api/version-history/#v1-21-api-changes)
52
+    1.8.x | [1.20](https://docs.docker.com/engine/api/v1.20/) | [API changes](https://docs.docker.com/engine/api/version-history/#v1-20-api-changes)
53
+    1.7.x | [1.19](https://docs.docker.com/engine/api/v1.19/) | [API changes](https://docs.docker.com/engine/api/version-history/#v1-19-api-changes)
54
+    1.6.x | [1.18](https://docs.docker.com/engine/api/v1.18/) | [API changes](https://docs.docker.com/engine/api/version-history/#v1-18-api-changes)
55 55
 
56 56
     # Authentication
57 57
 
... ...
@@ -68,7 +79,7 @@ info:
68 68
 
69 69
     The `serveraddress` is a domain/IP without a protocol. Throughout this structure, double quotes are required.
70 70
 
71
-    If you have already got an identity token from the [`/auth` endpoint](#operation/checkAuthentication), you can just pass this instead of credentials:
71
+    If you have already got an identity token from the [`/auth` endpoint](#operation/SystemAuth), you can just pass this instead of credentials:
72 72
 
73 73
     ```
74 74
     {
... ...
@@ -76,6 +87,48 @@ info:
76 76
     }
77 77
     ```
78 78
 
79
+# The tags on paths define the menu sections in the ReDoc documentation, so
80
+# the usage of tags must make sense for that:
81
+# - They should be singular, not plural.
82
+# - There should not be too many tags, or the menu becomes unwieldly. For
83
+#   example, it is preferable to add a path to the "System" tag instead of
84
+#   creating a tag with a single path in it.
85
+# - The order of tags in this list defines the order in the menu.
86
+tags:
87
+  # Primary objects
88
+  - name: "Container"
89
+    description: |
90
+      Create and manage containers.
91
+  - name: "Image"
92
+  - name: "Network"
93
+    description: |
94
+      Networks are user-defined networks that containers can be attached to. See the [networking documentation](https://docs.docker.com/engine/userguide/networking/) for more information.
95
+  - name: "Volume"
96
+    description: |
97
+      Create and manage persistent storage that can be attached to containers.
98
+  - name: "Exec"
99
+    description: |
100
+      Run new commands inside running containers. See the [command-line reference](https://docs.docker.com/engine/reference/commandline/exec/) for more information.
101
+
102
+      To exec a command in a container, you first need to create an exec instance, then start it. These two API endpoints are wrapped up in a single command-line command, `docker exec`.
103
+  - name: "Secret"
104
+  # Swarm things
105
+  - name: "Swarm"
106
+    description: |
107
+      Engines can be clustered together in a swarm. See [the swarm mode documentation](https://docs.docker.com/engine/swarm/) for more information.
108
+  - name: "Node"
109
+    description: |
110
+      Nodes are instances of the Engine participating in a swarm. Swarm mode must be enabled for these endpoints to work.
111
+  - name: "Service"
112
+    description: |
113
+      Services are the definitions of tasks to run on a swarm. Swarm mode must be enabled for these endpoints to work.
114
+  - name: "Task"
115
+    description: |
116
+      A task is a container running on a swarm. It is the atomic scheduling unit of swarm. Swarm mode must be enabled for these endpoints to work.
117
+  # System things
118
+  - name: "Plugin"
119
+  - name: "System"
120
+
79 121
 definitions:
80 122
   Port:
81 123
     type: "object"
... ...
@@ -2387,7 +2440,7 @@ paths:
2387 2387
   /containers/json:
2388 2388
     get:
2389 2389
       summary: "List containers"
2390
-      operationId: "GetContainerList"
2390
+      operationId: "ContainerList"
2391 2391
       produces:
2392 2392
         - "application/json"
2393 2393
       parameters:
... ...
@@ -2565,8 +2618,7 @@ paths:
2565 2565
           description: "server error"
2566 2566
           schema:
2567 2567
             $ref: "#/definitions/ErrorResponse"
2568
-      tags:
2569
-        - "Container"
2568
+      tags: ["Container"]
2570 2569
   /containers/create:
2571 2570
     post:
2572 2571
       summary: "Create a container"
... ...
@@ -2765,13 +2817,12 @@ paths:
2765 2765
           description: "server error"
2766 2766
           schema:
2767 2767
             $ref: "#/definitions/ErrorResponse"
2768
-      tags:
2769
-        - "Container"
2768
+      tags: ["Container"]
2770 2769
   /containers/{id}/json:
2771 2770
     get:
2772 2771
       summary: "Inspect a container"
2773 2772
       description: "Return low-level information about a container."
2774
-      operationId: "GetContainerInspect"
2773
+      operationId: "ContainerInspect"
2775 2774
       produces:
2776 2775
         - "application/json"
2777 2776
       responses:
... ...
@@ -3042,13 +3093,12 @@ paths:
3042 3042
           type: "boolean"
3043 3043
           default: false
3044 3044
           description: "Return the size of container as fields `SizeRw` and `SizeRootFs`"
3045
-      tags:
3046
-        - "Container"
3045
+      tags: ["Container"]
3047 3046
   /containers/{id}/top:
3048 3047
     get:
3049 3048
       summary: "List processes running inside a container"
3050 3049
       description: "On Unix systems, this is done by running the `ps` command. This endpoint is not supported on Windows."
3051
-      operationId: "GetContainerTop"
3050
+      operationId: "ContainerTop"
3052 3051
       responses:
3053 3052
         200:
3054 3053
           description: "no error"
... ...
@@ -3119,8 +3169,7 @@ paths:
3119 3119
           description: "The arguments to pass to `ps`. For example, `aux`"
3120 3120
           type: "string"
3121 3121
           default: "-ef"
3122
-      tags:
3123
-        - "Container"
3122
+      tags: ["Container"]
3124 3123
   /containers/{id}/logs:
3125 3124
     get:
3126 3125
       summary: "Get container logs"
... ...
@@ -3128,7 +3177,7 @@ paths:
3128 3128
         Get `stdout` and `stderr` logs from a container.
3129 3129
 
3130 3130
         Note: This endpoint works only for containers with the `json-file` or `journald` logging driver.
3131
-      operationId: "GetContainerLogs"
3131
+      operationId: "ContainerLogs"
3132 3132
       responses:
3133 3133
         101:
3134 3134
           description: "logs returned as a stream"
... ...
@@ -3161,7 +3210,7 @@ paths:
3161 3161
           description: |
3162 3162
             Return the logs as a stream.
3163 3163
 
3164
-            This will return a `101` HTTP response with a `Connection: upgrade` header, then hijack the HTTP connection to send raw output. For more information about hijacking and the stream format, [see the documentation for the attach endpoint](#operation/PostContainerAttach).
3164
+            This will return a `101` HTTP response with a `Connection: upgrade` header, then hijack the HTTP connection to send raw output. For more information about hijacking and the stream format, [see the documentation for the attach endpoint](#operation/ContainerAttach).
3165 3165
           type: "boolean"
3166 3166
           default: false
3167 3167
         - name: "stdout"
... ...
@@ -3189,8 +3238,7 @@ paths:
3189 3189
           description: "Only return this number of log lines from the end of the logs. Specify as an integer or `all` to output all log lines."
3190 3190
           type: "string"
3191 3191
           default: "all"
3192
-      tags:
3193
-        - "Container"
3192
+      tags: ["Container"]
3194 3193
   /containers/{id}/changes:
3195 3194
     get:
3196 3195
       summary: "Get changes on a container’s filesystem"
... ...
@@ -3200,7 +3248,7 @@ paths:
3200 3200
         - `0`: Modified
3201 3201
         - `1`: Added
3202 3202
         - `2`: Deleted
3203
-      operationId: "GetContainerChanges"
3203
+      operationId: "ContainerChanges"
3204 3204
       produces:
3205 3205
         - "application/json"
3206 3206
       responses:
... ...
@@ -3246,13 +3294,12 @@ paths:
3246 3246
           required: true
3247 3247
           description: "ID or name of the container"
3248 3248
           type: "string"
3249
-      tags:
3250
-        - "Container"
3249
+      tags: ["Container"]
3251 3250
   /containers/{id}/export:
3252 3251
     get:
3253 3252
       summary: "Export a container"
3254 3253
       description: "Export the contents of a container as a tarball."
3255
-      operationId: "GetContainerExport"
3254
+      operationId: "ContainerExport"
3256 3255
       produces:
3257 3256
         - "application/octet-stream"
3258 3257
       responses:
... ...
@@ -3275,8 +3322,7 @@ paths:
3275 3275
           required: true
3276 3276
           description: "ID or name of the container"
3277 3277
           type: "string"
3278
-      tags:
3279
-        - "Container"
3278
+      tags: ["Container"]
3280 3279
   /containers/{id}/stats:
3281 3280
     get:
3282 3281
       summary: "Get container stats based on resource usage"
... ...
@@ -3284,7 +3330,7 @@ paths:
3284 3284
         This endpoint returns a live stream of a container’s resource usage statistics.
3285 3285
 
3286 3286
         The `precpu_stats` is the CPU statistic of last read, which is used for calculating the CPU usage percentage. It is not the same as the `cpu_stats` field.
3287
-      operationId: "GetContainerStats"
3287
+      operationId: "ContainerStats"
3288 3288
       produces:
3289 3289
         - "application/json"
3290 3290
       responses:
... ...
@@ -3404,13 +3450,12 @@ paths:
3404 3404
           description: "Stream the output. If false, the stats will be output once and then it will disconnect."
3405 3405
           type: "boolean"
3406 3406
           default: true
3407
-      tags:
3408
-        - "Container"
3407
+      tags: ["Container"]
3409 3408
   /containers/{id}/resize:
3410 3409
     post:
3411 3410
       summary: "Resize a container TTY"
3412 3411
       description: "Resize the TTY for a container. You must restart the container for the resize to take effect."
3413
-      operationId: "PostContainerResize"
3412
+      operationId: "ContainerResize"
3414 3413
       consumes:
3415 3414
         - "application/octet-stream"
3416 3415
       produces:
... ...
@@ -3443,12 +3488,11 @@ paths:
3443 3443
           in: "query"
3444 3444
           description: "Width of the tty session in characters"
3445 3445
           type: "integer"
3446
-      tags:
3447
-        - "Container"
3446
+      tags: ["Container"]
3448 3447
   /containers/{id}/start:
3449 3448
     post:
3450 3449
       summary: "Start a container"
3451
-      operationId: "PostContainerStart"
3450
+      operationId: "ContainerStart"
3452 3451
       responses:
3453 3452
         204:
3454 3453
           description: "no error"
... ...
@@ -3477,12 +3521,11 @@ paths:
3477 3477
           in: "query"
3478 3478
           description: "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`."
3479 3479
           type: "string"
3480
-      tags:
3481
-        - "Container"
3480
+      tags: ["Container"]
3482 3481
   /containers/{id}/stop:
3483 3482
     post:
3484 3483
       summary: "Stop a container"
3485
-      operationId: "PostContainerStop"
3484
+      operationId: "ContainerStop"
3486 3485
       responses:
3487 3486
         204:
3488 3487
           description: "no error"
... ...
@@ -3511,12 +3554,11 @@ paths:
3511 3511
           in: "query"
3512 3512
           description: "Number of seconds to wait before killing the container"
3513 3513
           type: "integer"
3514
-      tags:
3515
-        - "Container"
3514
+      tags: ["Container"]
3516 3515
   /containers/{id}/restart:
3517 3516
     post:
3518 3517
       summary: "Restart a container"
3519
-      operationId: "PostContainerRestart"
3518
+      operationId: "ContainerRestart"
3520 3519
       responses:
3521 3520
         204:
3522 3521
           description: "no error"
... ...
@@ -3541,13 +3583,12 @@ paths:
3541 3541
           in: "query"
3542 3542
           description: "Number of seconds to wait before killing the container"
3543 3543
           type: "integer"
3544
-      tags:
3545
-        - "Container"
3544
+      tags: ["Container"]
3546 3545
   /containers/{id}/kill:
3547 3546
     post:
3548 3547
       summary: "Kill a container"
3549 3548
       description: "Send a POSIX signal to a container, defaulting to killing to the container."
3550
-      operationId: "PostContainerKill"
3549
+      operationId: "ContainerKill"
3551 3550
       responses:
3552 3551
         204:
3553 3552
           description: "no error"
... ...
@@ -3573,8 +3614,7 @@ paths:
3573 3573
           description: "Signal to send to the container as an integer or string (e.g. `SIGINT`)"
3574 3574
           type: "string"
3575 3575
           default: "SIGKILL"
3576
-      tags:
3577
-        - "Container"
3576
+      tags: ["Container"]
3578 3577
   /containers/{id}/update:
3579 3578
     post:
3580 3579
       summary: "Update a container"
... ...
@@ -3635,12 +3675,11 @@ paths:
3635 3635
               RestartPolicy:
3636 3636
                 MaximumRetryCount: 4
3637 3637
                 Name: "on-failure"
3638
-      tags:
3639
-        - "Container"
3638
+      tags: ["Container"]
3640 3639
   /containers/{id}/rename:
3641 3640
     post:
3642 3641
       summary: "Rename a container"
3643
-      operationId: "PostContainerRename"
3642
+      operationId: "ContainerRename"
3644 3643
       responses:
3645 3644
         204:
3646 3645
           description: "no error"
... ...
@@ -3670,8 +3709,7 @@ paths:
3670 3670
           required: true
3671 3671
           description: "New name for the container"
3672 3672
           type: "string"
3673
-      tags:
3674
-        - "Container"
3673
+      tags: ["Container"]
3675 3674
   /containers/{id}/pause:
3676 3675
     post:
3677 3676
       summary: "Pause a container"
... ...
@@ -3679,7 +3717,7 @@ paths:
3679 3679
         Use the cgroups freezer to suspend all processes in a container.
3680 3680
 
3681 3681
         Traditionally, when suspending a process the `SIGSTOP` signal is used, which is observable by the process being suspended. With the cgroups freezer the process is unaware, and unable to capture, that it is being suspended, and subsequently resumed.
3682
-      operationId: "PostContainerPause"
3682
+      operationId: "ContainerPause"
3683 3683
       responses:
3684 3684
         204:
3685 3685
           description: "no error"
... ...
@@ -3700,13 +3738,12 @@ paths:
3700 3700
           required: true
3701 3701
           description: "ID or name of the container"
3702 3702
           type: "string"
3703
-      tags:
3704
-        - "Container"
3703
+      tags: ["Container"]
3705 3704
   /containers/{id}/unpause:
3706 3705
     post:
3707 3706
       summary: "Unpause a container"
3708 3707
       description: "Resume a container which has been paused."
3709
-      operationId: "PostContainerUnpause"
3708
+      operationId: "ContainerUnpause"
3710 3709
       responses:
3711 3710
         204:
3712 3711
           description: "no error"
... ...
@@ -3727,8 +3764,7 @@ paths:
3727 3727
           required: true
3728 3728
           description: "ID or name of the container"
3729 3729
           type: "string"
3730
-      tags:
3731
-        - "Container"
3730
+      tags: ["Container"]
3732 3731
   /containers/{id}/attach:
3733 3732
     post:
3734 3733
       summary: "Attach to a container"
... ...
@@ -3737,7 +3773,7 @@ paths:
3737 3737
 
3738 3738
         Either the `stream` or `logs` parameter must be `true` for this endpoint to do anything.
3739 3739
 
3740
-        See [the documentation for the `docker attach` command](/engine/reference/commandline/attach/) for more details.
3740
+        See [the documentation for the `docker attach` command](https://docs.docker.com/engine/reference/commandline/attach/) for more details.
3741 3741
 
3742 3742
         ### Hijacking
3743 3743
 
... ...
@@ -3777,7 +3813,7 @@ paths:
3777 3777
 
3778 3778
         ### Stream format
3779 3779
 
3780
-        When the TTY setting is disabled in [`POST /containers/create`](#operation/PostContainerCreate), the stream over the hijacked connected is multiplexed to separate out `stdout` and `stderr`. The stream consists of a series of frames, each containing a header and a payload.
3780
+        When the TTY setting is disabled in [`POST /containers/create`](#operation/ContainerCreate), the stream over the hijacked connected is multiplexed to separate out `stdout` and `stderr`. The stream consists of a series of frames, each containing a header and a payload.
3781 3781
 
3782 3782
         The header contains the information which the stream writes (`stdout` or `stderr`). It also contains the size of the associated frame encoded in the last four bytes (`uint32`).
3783 3783
 
... ...
@@ -3807,9 +3843,9 @@ paths:
3807 3807
 
3808 3808
         ### Stream format when using a TTY
3809 3809
 
3810
-        When the TTY setting is enabled in [`POST /containers/create`](#operation/PostContainerCreate), the stream is not multiplexed. The data exchanged over the hijacked connection is simply the raw data from the process PTY and client's `stdin`.
3810
+        When the TTY setting is enabled in [`POST /containers/create`](#operation/ContainerCreate), the stream is not multiplexed. The data exchanged over the hijacked connection is simply the raw data from the process PTY and client's `stdin`.
3811 3811
 
3812
-      operationId: "PostContainerAttach"
3812
+      operationId: "ContainerAttach"
3813 3813
       produces:
3814 3814
         - "application/vnd.docker.raw-stream"
3815 3815
       responses:
... ...
@@ -3872,12 +3908,11 @@ paths:
3872 3872
           description: "Attach to `stderr`"
3873 3873
           type: "boolean"
3874 3874
           default: false
3875
-      tags:
3876
-        - "Container"
3875
+      tags: ["Container"]
3877 3876
   /containers/{id}/attach/ws:
3878 3877
     get:
3879 3878
       summary: "Attach to a container via a websocket"
3880
-      operationId: "PostContainerAttachWebsocket"
3879
+      operationId: "ContainerAttachWebsocket"
3881 3880
       responses:
3882 3881
         101:
3883 3882
           description: "no error, hints proxy about hijacking"
... ...
@@ -3933,8 +3968,7 @@ paths:
3933 3933
           description: "Attach to `stderr`"
3934 3934
           type: "boolean"
3935 3935
           default: false
3936
-      tags:
3937
-        - "Container"
3936
+      tags: ["Container"]
3938 3937
   /containers/{id}/wait:
3939 3938
     post:
3940 3939
       summary: "Wait for a container"
... ...
@@ -3969,12 +4003,11 @@ paths:
3969 3969
           required: true
3970 3970
           description: "ID or name of the container"
3971 3971
           type: "string"
3972
-      tags:
3973
-        - "Container"
3972
+      tags: ["Container"]
3974 3973
   /containers/{id}:
3975 3974
     delete:
3976 3975
       summary: "Remove a container"
3977
-      operationId: "DeleteContainer"
3976
+      operationId: "ContainerDelete"
3978 3977
       responses:
3979 3978
         204:
3980 3979
           description: "no error"
... ...
@@ -4009,13 +4042,12 @@ paths:
4009 4009
           description: "If the container is running, kill it before removing it."
4010 4010
           type: "boolean"
4011 4011
           default: false
4012
-      tags:
4013
-        - "Container"
4012
+      tags: ["Container"]
4014 4013
   /containers/{id}/archive:
4015 4014
     head:
4016 4015
       summary: "Get information about files in a container"
4017 4016
       description: "A response header `X-Docker-Container-Path-Stat` is return containing a base64 - encoded JSON object with some filesystem header information about the path."
4018
-      operationId: "HeadContainerArchive"
4017
+      operationId: "ContainerArchiveHead"
4019 4018
       responses:
4020 4019
         200:
4021 4020
           description: "no error"
... ...
@@ -4056,12 +4088,11 @@ paths:
4056 4056
           required: true
4057 4057
           description: "Resource in the container’s filesystem to archive."
4058 4058
           type: "string"
4059
-      tags:
4060
-        - "Container"
4059
+      tags: ["Container"]
4061 4060
     get:
4062 4061
       summary: "Get an archive of a filesystem resource in a container"
4063 4062
       description: "Get an tar archive of a resource in the filesystem of container id."
4064
-      operationId: "GetContainerArchive"
4063
+      operationId: "ContainerGetArchive"
4065 4064
       produces:
4066 4065
         - "application/x-tar"
4067 4066
       responses:
... ...
@@ -4100,12 +4131,11 @@ paths:
4100 4100
           required: true
4101 4101
           description: "Resource in the container’s filesystem to archive."
4102 4102
           type: "string"
4103
-      tags:
4104
-        - "Container"
4103
+      tags: ["Container"]
4105 4104
     put:
4106 4105
       summary: "Extract an archive of files or folders to a directory in a container"
4107 4106
       description: "Upload a tar archive to be extracted to a path in the filesystem of container id."
4108
-      operationId: "PutContainerArchive"
4107
+      operationId: "ContainerPutArchive"
4109 4108
       consumes:
4110 4109
         - "application/x-tar"
4111 4110
         - "application/octet-stream"
... ...
@@ -4152,8 +4182,7 @@ paths:
4152 4152
           description: "The input stream must be a tar archive compressed with one of the following algorithms: identity (no compression), gzip, bzip2, xz."
4153 4153
           schema:
4154 4154
             type: "string"
4155
-      tags:
4156
-        - "Container"
4155
+      tags: ["Container"]
4157 4156
   /containers/prune:
4158 4157
     post:
4159 4158
       summary: "Delete stopped containers"
... ...
@@ -4181,13 +4210,12 @@ paths:
4181 4181
           description: "Server error"
4182 4182
           schema:
4183 4183
             $ref: "#/definitions/ErrorResponse"
4184
-      tags:
4185
-        - "Container"
4184
+      tags: ["Container"]
4186 4185
   /images/json:
4187 4186
     get:
4188 4187
       summary: "List Images"
4189 4188
       description: "Returns a list of images on the server. Note that it uses a different, smaller representation of an image than inspecting a single image."
4190
-      operationId: "GetImageList"
4189
+      operationId: "ImageList"
4191 4190
       produces:
4192 4191
         - "application/json"
4193 4192
       responses:
... ...
@@ -4253,20 +4281,19 @@ paths:
4253 4253
           description: "Show digest information as a `RepoDigests` field on each image."
4254 4254
           type: "boolean"
4255 4255
           default: false
4256
-      tags:
4257
-        - "Image"
4256
+      tags: ["Image"]
4258 4257
   /build:
4259 4258
     post:
4260 4259
       summary: "Build an image"
4261 4260
       description: |
4262 4261
         Build an image from a tar archive with a `Dockerfile` in it.
4263 4262
 
4264
-        The `Dockerfile` specifies how the image is built from the tar archive. It is typically in the archive's root, but can be at a different path or have a different name by specifying the `dockerfile` parameter. [See the `Dockerfile` reference for more information](/engine/reference/builder/).
4263
+        The `Dockerfile` specifies how the image is built from the tar archive. It is typically in the archive's root, but can be at a different path or have a different name by specifying the `dockerfile` parameter. [See the `Dockerfile` reference for more information](https://docs.docker.com/engine/reference/builder/).
4265 4264
 
4266 4265
         The Docker daemon performs a preliminary validation of the `Dockerfile` before starting the build, and returns an error if the syntax is incorrect. After that, each instruction is run one-by-one until the ID of the new image is output.
4267 4266
 
4268 4267
         The build is canceled if the client drops the connection by quitting or being killed.
4269
-      operationId: "PostImageBuild"
4268
+      operationId: "ImageBuild"
4270 4269
       consumes:
4271 4270
         - "application/octet-stream"
4272 4271
       produces:
... ...
@@ -4345,7 +4372,7 @@ paths:
4345 4345
           type: "integer"
4346 4346
         - name: "buildargs"
4347 4347
           in: "query"
4348
-          description: "JSON map of string pairs for build-time variables. Users pass these values at build-time. Docker uses the buildargs as the environment context for commands run via the `Dockerfile` RUN instruction, or for variable expansion in other `Dockerfile` instructions. This is not meant for passing secret values. [Read more about the buildargs instruction.](/engine/reference/builder/#arg)"
4348
+          description: "JSON map of string pairs for build-time variables. Users pass these values at build-time. Docker uses the buildargs as the environment context for commands run via the `Dockerfile` RUN instruction, or for variable expansion in other `Dockerfile` instructions. This is not meant for passing secret values. [Read more about the buildargs instruction.](https://docs.docker.com/engine/reference/builder/#arg)"
4349 4349
           type: "integer"
4350 4350
         - name: "shmsize"
4351 4351
           in: "query"
... ...
@@ -4401,13 +4428,12 @@ paths:
4401 4401
           description: "server error"
4402 4402
           schema:
4403 4403
             $ref: "#/definitions/ErrorResponse"
4404
-      tags:
4405
-        - "Image"
4404
+      tags: ["Image"]
4406 4405
   /images/create:
4407 4406
     post:
4408 4407
       summary: "Create an image"
4409 4408
       description: "Create an image by either pulling it from a registry or importing it."
4410
-      operationId: "PostImageCreate"
4409
+      operationId: "ImageCreate"
4411 4410
       consumes:
4412 4411
         - "text/plain"
4413 4412
         - "application/octet-stream"
... ...
@@ -4447,13 +4473,12 @@ paths:
4447 4447
           in: "header"
4448 4448
           description: "A base64-encoded auth configuration. [See the authentication section for details.](#section/Authentication)"
4449 4449
           type: "string"
4450
-      tags:
4451
-        - "Image"
4450
+      tags: ["Image"]
4452 4451
   /images/{name}/json:
4453 4452
     get:
4454 4453
       summary: "Inspect an image"
4455 4454
       description: "Return low-level information about an image."
4456
-      operationId: "GetImageInspect"
4455
+      operationId: "ImageInspect"
4457 4456
       produces:
4458 4457
         - "application/json"
4459 4458
       responses:
... ...
@@ -4554,13 +4579,12 @@ paths:
4554 4554
           description: "Image name or id"
4555 4555
           type: "string"
4556 4556
           required: true
4557
-      tags:
4558
-        - "Image"
4557
+      tags: ["Image"]
4559 4558
   /images/{name}/history:
4560 4559
     get:
4561 4560
       summary: "Get the history of an image"
4562 4561
       description: "Return parent layers of an image."
4563
-      operationId: "GetImageHistory"
4562
+      operationId: "ImageHistory"
4564 4563
       produces:
4565 4564
         - "application/json"
4566 4565
       responses:
... ...
@@ -4625,8 +4649,7 @@ paths:
4625 4625
           description: "Image name or ID"
4626 4626
           type: "string"
4627 4627
           required: true
4628
-      tags:
4629
-        - "Image"
4628
+      tags: ["Image"]
4630 4629
   /images/{name}/push:
4631 4630
     post:
4632 4631
       summary: "Push an image"
... ...
@@ -4636,7 +4659,7 @@ paths:
4636 4636
         If you wish to push an image on to a private registry, that image must already have a tag which references the registry. For example, `registry.example.com/myimage:latest`.
4637 4637
 
4638 4638
         The push is cancelled if the HTTP connection is closed.
4639
-      operationId: "PostImagePush"
4639
+      operationId: "ImagePush"
4640 4640
       consumes:
4641 4641
         - "application/octet-stream"
4642 4642
       responses:
... ...
@@ -4665,13 +4688,12 @@ paths:
4665 4665
           description: "A base64-encoded auth configuration. [See the authentication section for details.](#section/Authentication)"
4666 4666
           type: "string"
4667 4667
           required: true
4668
-      tags:
4669
-        - "Image"
4668
+      tags: ["Image"]
4670 4669
   /images/{name}/tag:
4671 4670
     post:
4672 4671
       summary: "Tag an image"
4673 4672
       description: "Tag an image so that it becomes part of a repository."
4674
-      operationId: "PostImageTag"
4673
+      operationId: "ImageTag"
4675 4674
       responses:
4676 4675
         201:
4677 4676
           description: "No error"
... ...
@@ -4705,8 +4727,7 @@ paths:
4705 4705
           in: "query"
4706 4706
           description: "The name of the new tag."
4707 4707
           type: "string"
4708
-      tags:
4709
-        - "Image"
4708
+      tags: ["Image"]
4710 4709
   /images/{name}:
4711 4710
     delete:
4712 4711
       summary: "Remove an image"
... ...
@@ -4714,7 +4735,7 @@ paths:
4714 4714
         Remove an image, along with any untagged parent images that were referenced by that image.
4715 4715
 
4716 4716
         Images can't be removed if they have descendant images, are being used by a running container or are being used by a build.
4717
-      operationId: "DeleteImage"
4717
+      operationId: "ImageDelete"
4718 4718
       produces:
4719 4719
         - "application/json"
4720 4720
       responses:
... ...
@@ -4757,13 +4778,12 @@ paths:
4757 4757
           description: "Do not delete untagged parent images"
4758 4758
           type: "boolean"
4759 4759
           default: false
4760
-      tags:
4761
-        - "Image"
4760
+      tags: ["Image"]
4762 4761
   /images/search:
4763 4762
     get:
4764 4763
       summary: "Search images"
4765 4764
       description: "Search for an image on Docker Hub."
4766
-      operationId: "GetImageSearch"
4765
+      operationId: "ImageSearch"
4767 4766
       produces:
4768 4767
         - "application/json"
4769 4768
       responses:
... ...
@@ -4824,8 +4844,7 @@ paths:
4824 4824
             - `is-automated=(true|false)`
4825 4825
             - `is-official=(true|false)`
4826 4826
           type: "string"
4827
-      tags:
4828
-        - "Image"
4827
+      tags: ["Image"]
4829 4828
   /images/prune:
4830 4829
     post:
4831 4830
       summary: "Delete unused images"
... ...
@@ -4863,13 +4882,12 @@ paths:
4863 4863
           description: "Server error"
4864 4864
           schema:
4865 4865
             $ref: "#/definitions/ErrorResponse"
4866
-      tags:
4867
-        - "Image"
4866
+      tags: ["Image"]
4868 4867
   /auth:
4869 4868
     post:
4870 4869
       summary: "Check auth configuration"
4871 4870
       description: "Validate credentials for a registry and, if available, get an identity token for accessing the registry without password."
4872
-      operationId: "Authenticate"
4871
+      operationId: "SystemAuth"
4873 4872
       consumes: ["application/json"]
4874 4873
       produces: ["application/json"]
4875 4874
       responses:
... ...
@@ -4903,11 +4921,11 @@ paths:
4903 4903
           description: "Authentication to check"
4904 4904
           schema:
4905 4905
             $ref: "#/definitions/AuthConfig"
4906
-      tags: ["Registry"]
4906
+      tags: ["System"]
4907 4907
   /info:
4908 4908
     get:
4909 4909
       summary: "Get system information"
4910
-      operationId: "getSystemInformation"
4910
+      operationId: "SystemInfo"
4911 4911
       produces:
4912 4912
         - "application/json"
4913 4913
       responses:
... ...
@@ -5115,13 +5133,12 @@ paths:
5115 5115
           description: "Server error"
5116 5116
           schema:
5117 5117
             $ref: "#/definitions/ErrorResponse"
5118
-      tags:
5119
-        - "Misc"
5118
+      tags: ["System"]
5120 5119
   /version:
5121 5120
     get:
5122 5121
       summary: "Get version"
5123 5122
       description: "Returns the version of Docker that is running and various information about the system that Docker is running on."
5124
-      operationId: "getVersion"
5123
+      operationId: "SystemVersion"
5125 5124
       produces:
5126 5125
         - "application/json"
5127 5126
       responses:
... ...
@@ -5166,13 +5183,12 @@ paths:
5166 5166
           description: "server error"
5167 5167
           schema:
5168 5168
             $ref: "#/definitions/ErrorResponse"
5169
-      tags:
5170
-        - "Misc"
5169
+      tags: ["System"]
5171 5170
   /_ping:
5172 5171
     get:
5173 5172
       summary: "Ping"
5174 5173
       description: "This is a dummy endpoint you can use to test if the server is accessible."
5175
-      operationId: "ping"
5174
+      operationId: "SystemPing"
5176 5175
       produces:
5177 5176
         - "text/plain"
5178 5177
       responses:
... ...
@@ -5185,12 +5201,11 @@ paths:
5185 5185
           description: "server error"
5186 5186
           schema:
5187 5187
             $ref: "#/definitions/ErrorResponse"
5188
-      tags:
5189
-        - "Misc"
5188
+      tags: ["System"]
5190 5189
   /commit:
5191 5190
     post:
5192 5191
       summary: "Create a new image from a container"
5193
-      operationId: "PostImageCommit"
5192
+      operationId: "ImageCommit"
5194 5193
       consumes:
5195 5194
         - "application/json"
5196 5195
       produces:
... ...
@@ -5246,8 +5261,7 @@ paths:
5246 5246
           in: "query"
5247 5247
           description: "`Dockerfile` instructions to apply while committing"
5248 5248
           type: "string"
5249
-      tags:
5250
-        - "Image"
5249
+      tags: ["Image"]
5251 5250
   /events:
5252 5251
     get:
5253 5252
       summary: "Monitor events"
... ...
@@ -5266,7 +5280,7 @@ paths:
5266 5266
 
5267 5267
         The Docker daemon reports these events: `reload`
5268 5268
 
5269
-      operationId: "getEvents"
5269
+      operationId: "SystemEvents"
5270 5270
       produces:
5271 5271
         - "application/json"
5272 5272
       responses:
... ...
@@ -5337,11 +5351,11 @@ paths:
5337 5337
             - `network=<string>` network name or ID
5338 5338
             - `daemon=<string>` daemon name or ID
5339 5339
           type: "string"
5340
-      tags:
5341
-        - "Misc"
5340
+      tags: ["System"]
5342 5341
   /system/df:
5343 5342
     get:
5344 5343
       summary: "Get data usage information"
5344
+      operationId: "SystemDataUsage"
5345 5345
       responses:
5346 5346
         200:
5347 5347
           description: "no error"
... ...
@@ -5426,8 +5440,7 @@ paths:
5426 5426
           description: "server error"
5427 5427
           schema:
5428 5428
             $ref: "#/definitions/ErrorResponse"
5429
-      tags:
5430
-        - "Misc"
5429
+      tags: ["System"]
5431 5430
   /images/{name}/get:
5432 5431
     get:
5433 5432
       summary: "Export an image"
... ...
@@ -5455,7 +5468,7 @@ paths:
5455 5455
           }
5456 5456
         }
5457 5457
         ```
5458
-      operationId: "GetImage"
5458
+      operationId: "ImageGet"
5459 5459
       produces:
5460 5460
         - "application/x-tar"
5461 5461
       responses:
... ...
@@ -5474,8 +5487,7 @@ paths:
5474 5474
           description: "Image name or ID"
5475 5475
           type: "string"
5476 5476
           required: true
5477
-      tags:
5478
-        - "Image"
5477
+      tags: ["Image"]
5479 5478
   /images/get:
5480 5479
     get:
5481 5480
       summary: "Export several images"
... ...
@@ -5484,8 +5496,8 @@ paths:
5484 5484
 
5485 5485
         For each value of the `names` parameter: if it is a specific name and tag (e.g. `ubuntu:latest`), then only that image (and its parents) are returned; if it is an image ID, similarly only that image (and its parents) are returned and there would be no names referenced in the 'repositories' file for this image ID.
5486 5486
 
5487
-        For details on the format, see [the export image endpoint](#operation/GetImage).
5488
-      operationId: "GetImageSaveAll"
5487
+        For details on the format, see [the export image endpoint](#operation/ImageGet).
5488
+      operationId: "ImageGetAll"
5489 5489
       produces:
5490 5490
         - "application/x-tar"
5491 5491
       responses:
... ...
@@ -5505,16 +5517,15 @@ paths:
5505 5505
           type: "array"
5506 5506
           items:
5507 5507
             type: "string"
5508
-      tags:
5509
-        - "Image"
5508
+      tags: ["Image"]
5510 5509
   /images/load:
5511 5510
     post:
5512 5511
       summary: "Import images"
5513 5512
       description: |
5514 5513
         Load a set of images and tags into a repository.
5515 5514
 
5516
-        For details on the format, see [the export image endpoint](#operation/GetImage).
5517
-      operationId: "PostImageLoad"
5515
+        For details on the format, see [the export image endpoint](#operation/ImageGet).
5516
+      operationId: "ImageLoad"
5518 5517
       consumes:
5519 5518
         - "application/x-tar"
5520 5519
       produces:
... ...
@@ -5538,13 +5549,12 @@ paths:
5538 5538
           description: "Suppress progress details during load."
5539 5539
           type: "boolean"
5540 5540
           default: false
5541
-      tags:
5542
-        - "Image"
5541
+      tags: ["Image"]
5543 5542
   /containers/{id}/exec:
5544 5543
     post:
5545 5544
       summary: "Create an exec instance"
5546 5545
       description: "Run a command inside a running container."
5547
-      operationId: "PostContainerExec"
5546
+      operationId: "ContainerExec"
5548 5547
       consumes:
5549 5548
         - "application/json"
5550 5549
       produces:
... ...
@@ -5625,13 +5635,12 @@ paths:
5625 5625
           description: "ID or name of container"
5626 5626
           type: "string"
5627 5627
           required: true
5628
-      tags:
5629
-        - "Exec"
5628
+      tags: ["Exec"]
5630 5629
   /exec/{id}/start:
5631 5630
     post:
5632 5631
       summary: "Start an exec instance"
5633 5632
       description: "Starts a previously set up exec instance. If detach is true, this endpoint returns immediately after starting the command. Otherwise, it sets up an interactive session with the command."
5634
-      operationId: "PostExecStart"
5633
+      operationId: "ExecStart"
5635 5634
       consumes:
5636 5635
         - "application/json"
5637 5636
       produces:
... ...
@@ -5667,13 +5676,12 @@ paths:
5667 5667
           description: "Exec instance ID"
5668 5668
           required: true
5669 5669
           type: "string"
5670
-      tags:
5671
-        - "Exec"
5670
+      tags: ["Exec"]
5672 5671
   /exec/{id}/resize:
5673 5672
     post:
5674 5673
       summary: "Resize an exec instance"
5675 5674
       description: "Resize the TTY session used by an exec instance. This endpoint only works if `tty` was specified as part of creating and starting the exec instance."
5676
-      operationId: "PostExecResize"
5675
+      operationId: "ExecResize"
5677 5676
       responses:
5678 5677
         201:
5679 5678
           description: "No error"
... ...
@@ -5695,13 +5703,12 @@ paths:
5695 5695
           in: "query"
5696 5696
           description: "Width of the TTY session in characters"
5697 5697
           type: "integer"
5698
-      tags:
5699
-        - "Exec"
5698
+      tags: ["Exec"]
5700 5699
   /exec/{id}/json:
5701 5700
     get:
5702 5701
       summary: "Inspect an exec instance"
5703 5702
       description: "Return low-level information about an exec instance."
5704
-      operationId: "PostExecInspect"
5703
+      operationId: "ExecInspect"
5705 5704
       produces:
5706 5705
         - "application/json"
5707 5706
       responses:
... ...
@@ -5763,13 +5770,12 @@ paths:
5763 5763
           description: "Exec instance ID"
5764 5764
           required: true
5765 5765
           type: "string"
5766
-      tags:
5767
-        - "Exec"
5766
+      tags: ["Exec"]
5768 5767
 
5769 5768
   /volumes:
5770 5769
     get:
5771 5770
       summary: "List volumes"
5772
-      operationId: "VolumesList"
5771
+      operationId: "VolumeList"
5773 5772
       produces: ["application/json"]
5774 5773
       responses:
5775 5774
         200:
... ...
@@ -5831,7 +5837,7 @@ paths:
5831 5831
   /volumes/create:
5832 5832
     post:
5833 5833
       summary: "Create a volume"
5834
-      operationId: "VolumesCreate"
5834
+      operationId: "VolumeCreate"
5835 5835
       consumes: ["application/json"]
5836 5836
       produces: ["application/json"]
5837 5837
       responses:
... ...
@@ -5881,7 +5887,7 @@ paths:
5881 5881
   /volumes/{name}:
5882 5882
     get:
5883 5883
       summary: "Inspect a volume"
5884
-      operationId: "VolumesInspect"
5884
+      operationId: "VolumeInspect"
5885 5885
       produces: ["application/json"]
5886 5886
       responses:
5887 5887
         200:
... ...
@@ -5907,7 +5913,7 @@ paths:
5907 5907
     delete:
5908 5908
       summary: "Remove a volume"
5909 5909
       description: "Instruct the driver to remove the volume."
5910
-      operationId: "VolumesDelete"
5910
+      operationId: "VolumeDelete"
5911 5911
       responses:
5912 5912
         204:
5913 5913
           description: "The volume was removed"
... ...
@@ -5962,12 +5968,11 @@ paths:
5962 5962
           description: "Server error"
5963 5963
           schema:
5964 5964
             $ref: "#/definitions/ErrorResponse"
5965
-      tags:
5966
-        - "Volume"
5965
+      tags: ["Volume"]
5967 5966
   /networks:
5968 5967
     get:
5969 5968
       summary: "List networks"
5970
-      operationId: "NetworksList"
5969
+      operationId: "NetworkList"
5971 5970
       produces:
5972 5971
         - "application/json"
5973 5972
       responses:
... ...
@@ -6049,7 +6054,7 @@ paths:
6049 6049
   /networks/{id}:
6050 6050
     get:
6051 6051
       summary: "Inspect a network"
6052
-      operationId: "NetworksInspect"
6052
+      operationId: "NetworkInspect"
6053 6053
       produces:
6054 6054
         - "application/json"
6055 6055
       responses:
... ...
@@ -6071,7 +6076,7 @@ paths:
6071 6071
 
6072 6072
     delete:
6073 6073
       summary: "Remove a network"
6074
-      operationId: "DeleteNetworks"
6074
+      operationId: "NetworkDelete"
6075 6075
       responses:
6076 6076
         204:
6077 6077
           description: "No error"
... ...
@@ -6094,7 +6099,7 @@ paths:
6094 6094
   /networks/create:
6095 6095
     post:
6096 6096
       summary: "Create a network"
6097
-      operationId: "NetworksCreate"
6097
+      operationId: "NetworkCreate"
6098 6098
       consumes:
6099 6099
         - "application/json"
6100 6100
       produces:
... ...
@@ -6190,7 +6195,7 @@ paths:
6190 6190
   /networks/{id}/connect:
6191 6191
     post:
6192 6192
       summary: "Connect a container to a network"
6193
-      operationId: "NetworksConnect"
6193
+      operationId: "NetworkConnect"
6194 6194
       consumes:
6195 6195
         - "application/octet-stream"
6196 6196
       responses:
... ...
@@ -6236,7 +6241,7 @@ paths:
6236 6236
   /networks/{id}/disconnect:
6237 6237
     post:
6238 6238
       summary: "Disconnect a container from a network"
6239
-      operationId: "NetworksDisconnect"
6239
+      operationId: "NetworkDisconnect"
6240 6240
       consumes:
6241 6241
         - "application/json"
6242 6242
       responses:
... ...
@@ -6296,12 +6301,11 @@ paths:
6296 6296
           description: "Server error"
6297 6297
           schema:
6298 6298
             $ref: "#/definitions/ErrorResponse"
6299
-      tags:
6300
-        - "Network"
6299
+      tags: ["Network"]
6301 6300
   /plugins:
6302 6301
     get:
6303 6302
       summary: "List plugins"
6304
-      operationId: "PluginsList"
6303
+      operationId: "PluginList"
6305 6304
       description: "Returns information about installed plugins."
6306 6305
       produces: ["application/json"]
6307 6306
       responses:
... ...
@@ -6390,14 +6394,14 @@ paths:
6390 6390
           description: "Server error"
6391 6391
           schema:
6392 6392
             $ref: "#/definitions/ErrorResponse"
6393
-      tags: ["Plugins"]
6393
+      tags: ["Plugin"]
6394 6394
 
6395 6395
   /plugins/pull:
6396 6396
     post:
6397 6397
       summary: "Install a plugin"
6398
-      operationId: "PostPluginsPull"
6398
+      operationId: "PluginPull"
6399 6399
       description: |
6400
-        Pulls and installs a plugin. After the plugin is installed, it can be enabled using the [`POST /plugins/{name}/enable` endpoint](#operation/PostPluginsEnable).
6400
+        Pulls and installs a plugin. After the plugin is installed, it can be enabled using the [`POST /plugins/{name}/enable` endpoint](#operation/PluginEnable).
6401 6401
       produces:
6402 6402
         - "application/json"
6403 6403
       responses:
... ...
@@ -6447,12 +6451,11 @@ paths:
6447 6447
           in: "header"
6448 6448
           description: "A base64-encoded auth configuration to use when pulling a plugin from a registry. [See the authentication section for details.](#section/Authentication)"
6449 6449
           type: "string"
6450
-      tags:
6451
-        - "Plugins"
6450
+      tags: ["Plugin"]
6452 6451
   /plugins/{name}:
6453 6452
     get:
6454 6453
       summary: "Inspect a plugin"
6455
-      operationId: "GetPluginsInspect"
6454
+      operationId: "PluginInspect"
6456 6455
       responses:
6457 6456
         200:
6458 6457
           description: "no error"
... ...
@@ -6472,11 +6475,10 @@ paths:
6472 6472
           description: "The name of the plugin. The `:latest` tag is optional, and is the default if omitted."
6473 6473
           required: true
6474 6474
           type: "string"
6475
-      tags:
6476
-        - "Plugins"
6475
+      tags: ["Plugin"]
6477 6476
     delete:
6478 6477
       summary: "Remove a plugin"
6479
-      operationId: "DeletePlugins"
6478
+      operationId: "PluginDelete"
6480 6479
       responses:
6481 6480
         200:
6482 6481
           description: "no error"
... ...
@@ -6501,12 +6503,11 @@ paths:
6501 6501
           description: "Disable the plugin before removing. This may result in issues if the plugin is in use by a container."
6502 6502
           type: "boolean"
6503 6503
           default: false
6504
-      tags:
6505
-        - "Plugins"
6504
+      tags: ["Plugin"]
6506 6505
   /plugins/{name}/enable:
6507 6506
     post:
6508 6507
       summary: "Enable a plugin"
6509
-      operationId: "PostPluginsEnable"
6508
+      operationId: "PluginEnable"
6510 6509
       responses:
6511 6510
         200:
6512 6511
           description: "no error"
... ...
@@ -6525,12 +6526,11 @@ paths:
6525 6525
           description: "Set the HTTP client timeout (in seconds)"
6526 6526
           type: "integer"
6527 6527
           default: 0
6528
-      tags:
6529
-        - "Plugins"
6528
+      tags: ["Plugin"]
6530 6529
   /plugins/{name}/disable:
6531 6530
     post:
6532 6531
       summary: "Disable a plugin"
6533
-      operationId: "PostPluginsDisable"
6532
+      operationId: "PluginDisable"
6534 6533
       responses:
6535 6534
         200:
6536 6535
           description: "no error"
... ...
@@ -6544,12 +6544,11 @@ paths:
6544 6544
           description: "The name of the plugin. The `:latest` tag is optional, and is the default if omitted."
6545 6545
           required: true
6546 6546
           type: "string"
6547
-      tags:
6548
-        - "Plugins"
6547
+      tags: ["Plugin"]
6549 6548
   /plugins/create:
6550 6549
     post:
6551 6550
       summary: "Create a plugin"
6552
-      operationId: "PostPluginsCreate"
6551
+      operationId: "PluginCreate"
6553 6552
       consumes:
6554 6553
         - "application/x-tar"
6555 6554
       responses:
... ...
@@ -6571,8 +6570,7 @@ paths:
6571 6571
           schema:
6572 6572
             type: "string"
6573 6573
             format: "binary"
6574
-      tags:
6575
-        - "Plugins"
6574
+      tags: ["Plugin"]
6576 6575
   /plugins/{name}/push:
6577 6576
     post:
6578 6577
       summary: "Push a plugin"
... ...
@@ -6596,12 +6594,11 @@ paths:
6596 6596
           description: "server error"
6597 6597
           schema:
6598 6598
             $ref: "#/definitions/ErrorResponse"
6599
-      tags:
6600
-        - "Plugins"
6599
+      tags: ["Plugin"]
6601 6600
   /plugins/{name}/set:
6602 6601
     post:
6603 6602
       summary: "Configure a plugin"
6604
-      operationId: "PostPluginsSet"
6603
+      operationId: "PluginSet"
6605 6604
       consumes:
6606 6605
         - "application/json"
6607 6606
       parameters:
... ...
@@ -6628,12 +6625,11 @@ paths:
6628 6628
           description: "Server error"
6629 6629
           schema:
6630 6630
             $ref: "#/definitions/ErrorResponse"
6631
-      tags:
6632
-        - "Plugins"
6631
+      tags: ["Plugin"]
6633 6632
   /nodes:
6634 6633
     get:
6635 6634
       summary: "List nodes"
6636
-      operationId: "GetNodesList"
6635
+      operationId: "NodeList"
6637 6636
       responses:
6638 6637
         200:
6639 6638
           description: "no error"
... ...
@@ -6658,12 +6654,11 @@ paths:
6658 6658
             - `name=<node name>`
6659 6659
             - `role=`(`manager`|`worker`)`
6660 6660
           type: "string"
6661
-      tags:
6662
-        - "Nodes"
6661
+      tags: ["Node"]
6663 6662
   /nodes/{id}:
6664 6663
     get:
6665 6664
       summary: "Inspect a node"
6666
-      operationId: "GetNodesInspect"
6665
+      operationId: "NodeInspect"
6667 6666
       responses:
6668 6667
         200:
6669 6668
           description: "no error"
... ...
@@ -6683,11 +6678,10 @@ paths:
6683 6683
           description: "The ID or name of the node"
6684 6684
           type: "string"
6685 6685
           required: true
6686
-      tags:
6687
-        - "Nodes"
6686
+      tags: ["Node"]
6688 6687
     delete:
6689 6688
       summary: "Delete a node"
6690
-      operationId: "DeleteNodes"
6689
+      operationId: "NodeDelete"
6691 6690
       responses:
6692 6691
         200:
6693 6692
           description: "no error"
... ...
@@ -6710,12 +6704,11 @@ paths:
6710 6710
           description: "Force remove a node from the swarm"
6711 6711
           default: false
6712 6712
           type: "boolean"
6713
-      tags:
6714
-        - "Nodes"
6713
+      tags: ["Node"]
6715 6714
   /nodes/{id}/update:
6716 6715
     post:
6717 6716
       summary: "Update a node"
6718
-      operationId: "PostNodesUpdate"
6717
+      operationId: "NodeUpdate"
6719 6718
       responses:
6720 6719
         200:
6721 6720
           description: "no error"
... ...
@@ -6743,12 +6736,11 @@ paths:
6743 6743
           type: "integer"
6744 6744
           format: "int64"
6745 6745
           required: true
6746
-      tags:
6747
-        - "Nodes"
6746
+      tags: ["Node"]
6748 6747
   /swarm:
6749 6748
     get:
6750 6749
       summary: "Inspect swarm"
6751
-      operationId: "GetSwarmInspect"
6750
+      operationId: "SwarmInspect"
6752 6751
       responses:
6753 6752
         200:
6754 6753
           description: "no error"
... ...
@@ -6796,12 +6788,11 @@ paths:
6796 6796
           description: "server error"
6797 6797
           schema:
6798 6798
             $ref: "#/definitions/ErrorResponse"
6799
-      tags:
6800
-        - "Swarm"
6799
+      tags: ["Swarm"]
6801 6800
   /swarm/init:
6802 6801
     post:
6803 6802
       summary: "Initialize a new swarm"
6804
-      operationId: "PostSwarmInit"
6803
+      operationId: "SwarmInit"
6805 6804
       produces:
6806 6805
         - "application/json"
6807 6806
         - "text/plain"
... ...
@@ -6853,12 +6844,11 @@ paths:
6853 6853
                 CAConfig: {}
6854 6854
                 EncryptionConfig:
6855 6855
                   AutoLockManagers: false
6856
-      tags:
6857
-        - "Swarm"
6856
+      tags: ["Swarm"]
6858 6857
   /swarm/join:
6859 6858
     post:
6860 6859
       summary: "Join an existing swarm"
6861
-      operationId: "PostSwarmJoin"
6860
+      operationId: "SwarmJoin"
6862 6861
       responses:
6863 6862
         200:
6864 6863
           description: "no error"
... ...
@@ -6899,12 +6889,11 @@ paths:
6899 6899
               RemoteAddrs:
6900 6900
                 - "node1:2377"
6901 6901
               JoinToken: "SWMTKN-1-3pu6hszjas19xyp7ghgosyx9k8atbfcr8p2is99znpy26u2lkl-7p73s1dx5in4tatdymyhg9hu2"
6902
-      tags:
6903
-        - "Swarm"
6902
+      tags: ["Swarm"]
6904 6903
   /swarm/leave:
6905 6904
     post:
6906 6905
       summary: "Leave a swarm"
6907
-      operationId: "PostSwarmLeave"
6906
+      operationId: "SwarmLeave"
6908 6907
       responses:
6909 6908
         200:
6910 6909
           description: "no error"
... ...
@@ -6922,12 +6911,11 @@ paths:
6922 6922
           in: "query"
6923 6923
           type: "boolean"
6924 6924
           default: false
6925
-      tags:
6926
-        - "Swarm"
6925
+      tags: ["Swarm"]
6927 6926
   /swarm/update:
6928 6927
     post:
6929 6928
       summary: "Update a swarm"
6930
-      operationId: "PostSwarmUpdate"
6929
+      operationId: "SwarmUpdate"
6931 6930
       responses:
6932 6931
         200:
6933 6932
           description: "no error"
... ...
@@ -6970,8 +6958,7 @@ paths:
6970 6970
           description: "Rotate the manager unlock key."
6971 6971
           type: "boolean"
6972 6972
           default: false
6973
-      tags:
6974
-        - "Swarm"
6973
+      tags: ["Swarm"]
6975 6974
   /swarm/unlockkey:
6976 6975
     get:
6977 6976
       summary: "Get the unlock key"
... ...
@@ -6993,8 +6980,7 @@ paths:
6993 6993
           description: "server error"
6994 6994
           schema:
6995 6995
             $ref: "#/definitions/ErrorResponse"
6996
-      tags:
6997
-        - "Swarm"
6996
+      tags: ["Swarm"]
6998 6997
   /swarm/unlock:
6999 6998
     post:
7000 6999
       summary: "Unlock a locked manager"
... ...
@@ -7022,12 +7008,11 @@ paths:
7022 7022
           description: "server error"
7023 7023
           schema:
7024 7024
             $ref: "#/definitions/ErrorResponse"
7025
-      tags:
7026
-        - "Swarm"
7025
+      tags: ["Swarm"]
7027 7026
   /services:
7028 7027
     get:
7029 7028
       summary: "List services"
7030
-      operationId: "GetServicesList"
7029
+      operationId: "ServiceList"
7031 7030
       responses:
7032 7031
         200:
7033 7032
           description: "no error"
... ...
@@ -7049,12 +7034,11 @@ paths:
7049 7049
             - `id=<service id>`
7050 7050
             - `name=<service name>`
7051 7051
             - `label=<service label>`
7052
-      tags:
7053
-        - "Services"
7052
+      tags: ["Service"]
7054 7053
   /services/create:
7055 7054
     post:
7056 7055
       summary: "Create a service"
7057
-      operationId: "PostServicesCreate"
7056
+      operationId: "ServiceCreate"
7058 7057
       consumes:
7059 7058
         - "application/json"
7060 7059
       produces:
... ...
@@ -7147,12 +7131,11 @@ paths:
7147 7147
           in: "header"
7148 7148
           description: "A base64-encoded auth configuration for pulling from private registries. [See the authentication section for details.](#section/Authentication)"
7149 7149
           type: "string"
7150
-      tags:
7151
-        - "Services"
7150
+      tags: ["Service"]
7152 7151
   /services/{id}:
7153 7152
     get:
7154 7153
       summary: "Inspect a service"
7155
-      operationId: "GetServicesInspect"
7154
+      operationId: "ServiceInspect"
7156 7155
       responses:
7157 7156
         200:
7158 7157
           description: "no error"
... ...
@@ -7172,11 +7155,10 @@ paths:
7172 7172
           description: "ID or name of service."
7173 7173
           required: true
7174 7174
           type: "string"
7175
-      tags:
7176
-        - "Services"
7175
+      tags: ["Service"]
7177 7176
     delete:
7178 7177
       summary: "Delete a service"
7179
-      operationId: "DeleteServices"
7178
+      operationId: "ServiceDelete"
7180 7179
       responses:
7181 7180
         200:
7182 7181
           description: "no error"
... ...
@@ -7194,8 +7176,7 @@ paths:
7194 7194
           description: "ID or name of service."
7195 7195
           required: true
7196 7196
           type: "string"
7197
-      tags:
7198
-        - "Services"
7197
+      tags: ["Service"]
7199 7198
   /services/{id}/update:
7200 7199
     post:
7201 7200
       summary: "Update a service"
... ...
@@ -7270,7 +7251,7 @@ paths:
7270 7270
           description: "A base64-encoded auth configuration for pulling from private registries. [See the authentication section for details.](#section/Authentication)"
7271 7271
           type: "string"
7272 7272
 
7273
-      tags: ["Services"]
7273
+      tags: ["Service"]
7274 7274
   /services/{id}/logs:
7275 7275
     get:
7276 7276
       summary: "Get service logs"
... ...
@@ -7319,7 +7300,7 @@ paths:
7319 7319
           description: |
7320 7320
             Return the logs as a stream.
7321 7321
 
7322
-            This will return a `101` HTTP response with a `Connection: upgrade` header, then hijack the HTTP connection to send raw output. For more information about hijacking and the stream format, [see the documentation for the attach endpoint](#operation/PostContainerAttach).
7322
+            This will return a `101` HTTP response with a `Connection: upgrade` header, then hijack the HTTP connection to send raw output. For more information about hijacking and the stream format, [see the documentation for the attach endpoint](#operation/ContainerAttach).
7323 7323
           type: "boolean"
7324 7324
           default: false
7325 7325
         - name: "stdout"
... ...
@@ -7347,12 +7328,11 @@ paths:
7347 7347
           description: "Only return this number of log lines from the end of the logs. Specify as an integer or `all` to output all log lines."
7348 7348
           type: "string"
7349 7349
           default: "all"
7350
-      tags:
7351
-        - "Services"
7350
+      tags: ["Service"]
7352 7351
   /tasks:
7353 7352
     get:
7354 7353
       summary: "List tasks"
7355
-      operationId: "GetTasksList"
7354
+      operationId: "TaskList"
7356 7355
       produces:
7357 7356
         - "application/json"
7358 7357
       responses:
... ...
@@ -7491,12 +7471,11 @@ paths:
7491 7491
             - `node=<node id or name>`
7492 7492
             - `label=key` or `label="key=value"`
7493 7493
             - `desired-state=(running | shutdown | accepted)`
7494
-      tags:
7495
-        - "Tasks"
7494
+      tags: ["Task"]
7496 7495
   /tasks/{id}:
7497 7496
     get:
7498 7497
       summary: "Inspect a task"
7499
-      operationId: "GetTasksInspect"
7498
+      operationId: "TaskInspect"
7500 7499
       produces:
7501 7500
         - "application/json"
7502 7501
       responses:
... ...
@@ -7518,8 +7497,7 @@ paths:
7518 7518
           description: "ID of the task"
7519 7519
           required: true
7520 7520
           type: "string"
7521
-      tags:
7522
-        - "Tasks"
7521
+      tags: ["Task"]
7523 7522
   /secrets:
7524 7523
     get:
7525 7524
       summary: "List secrets"
... ...
@@ -7553,8 +7531,7 @@ paths:
7553 7553
             A JSON encoded value of the filters (a `map[string][]string`) to process on the secrets list. Available filters:
7554 7554
 
7555 7555
             - `names=<secret name>`
7556
-      tags:
7557
-        - "Secrets"
7556
+      tags: ["Secret"]
7558 7557
   /secrets/create:
7559 7558
     post:
7560 7559
       summary: "Create a secret"
... ...
@@ -7598,12 +7575,11 @@ paths:
7598 7598
                   Labels:
7599 7599
                     foo: "bar"
7600 7600
                   Data: "VEhJUyBJUyBOT1QgQSBSRUFMIENFUlRJRklDQVRFCg=="
7601
-      tags:
7602
-        - "Secrets"
7601
+      tags: ["Secret"]
7603 7602
   /secrets/{id}:
7604 7603
     get:
7605 7604
       summary: "Inspect a secret"
7606
-      operationId: "SecretsInspect"
7605
+      operationId: "SecretInspect"
7607 7606
       produces:
7608 7607
         - "application/json"
7609 7608
       responses:
... ...
@@ -7637,11 +7613,10 @@ paths:
7637 7637
           required: true
7638 7638
           type: "string"
7639 7639
           description: "ID of the secret"
7640
-      tags:
7641
-        - "Secrets"
7640
+      tags: ["Secret"]
7642 7641
     delete:
7643 7642
       summary: "Delete a secret"
7644
-      operationId: "SecretsDelete"
7643
+      operationId: "SecretDelete"
7645 7644
       produces:
7646 7645
         - "application/json"
7647 7646
       responses:
... ...
@@ -7661,5 +7636,4 @@ paths:
7661 7661
           required: true
7662 7662
           type: "string"
7663 7663
           description: "ID of the secret"
7664
-      tags:
7665
-        - "Secrets"
7664
+      tags: ["Secret"]
... ...
@@ -71,14 +71,20 @@ func env(b *Builder, args []string, attributes map[string]bool, original string)
71 71
 		if len(args[j]) == 0 {
72 72
 			return errBlankCommandNames("ENV")
73 73
 		}
74
-
75 74
 		newVar := args[j] + "=" + args[j+1] + ""
76 75
 		commitStr += " " + newVar
77 76
 
78 77
 		gotOne := false
79 78
 		for i, envVar := range b.runConfig.Env {
80 79
 			envParts := strings.SplitN(envVar, "=", 2)
81
-			if envParts[0] == args[j] {
80
+			compareFrom := envParts[0]
81
+			compareTo := args[j]
82
+			if runtime.GOOS == "windows" {
83
+				// Case insensitive environment variables on Windows
84
+				compareFrom = strings.ToUpper(compareFrom)
85
+				compareTo = strings.ToUpper(compareTo)
86
+			}
87
+			if compareFrom == compareTo {
82 88
 				b.runConfig.Env[i] = newVar
83 89
 				gotOne = true
84 90
 				break
... ...
@@ -1,112 +1,116 @@
1
-hello                    |     hello
2
-he'll'o                  |     hello
3
-he'llo                   |     hello
4
-he\'llo                  |     he'llo
5
-he\\'llo                 |     he\llo
6
-abc\tdef                 |     abctdef
7
-"abc\tdef"               |     abc\tdef
8
-'abc\tdef'               |     abc\tdef
9
-hello\                   |     hello
10
-hello\\                  |     hello\
11
-"hello                   |     hello
12
-"hello\"                 |     hello"
13
-"hel'lo"                 |     hel'lo
14
-'hello                   |     hello
15
-'hello\'                 |     hello\
16
-"''"                     |     ''
17
-$.                       |     $.
18
-$1                       |
19
-he$1x                    |     hex
20
-he$.x                    |     he$.x
21
-he$pwd.                  |     he.
22
-he$PWD                   |     he/home
23
-he\$PWD                  |     he$PWD
24
-he\\$PWD                 |     he\/home
25
-he\${}                   |     he${}
26
-he\${}xx                 |     he${}xx
27
-he${}                    |     he
28
-he${}xx                  |     hexx
29
-he${hi}                  |     he
30
-he${hi}xx                |     hexx
31
-he${PWD}                 |     he/home
32
-he${.}                   |     error
33
-he${XXX:-000}xx          |     he000xx
34
-he${PWD:-000}xx          |     he/homexx
35
-he${XXX:-$PWD}xx         |     he/homexx
36
-he${XXX:-${PWD:-yyy}}xx  |     he/homexx
37
-he${XXX:-${YYY:-yyy}}xx  |     heyyyxx
38
-he${XXX:YYY}             |     error
39
-he${XXX:+${PWD}}xx       |     hexx
40
-he${PWD:+${XXX}}xx       |     hexx
41
-he${PWD:+${SHELL}}xx     |     hebashxx
42
-he${XXX:+000}xx          |     hexx
43
-he${PWD:+000}xx          |     he000xx
44
-'he${XX}'                |     he${XX}
45
-"he${PWD}"               |     he/home
46
-"he'$PWD'"               |     he'/home'
47
-"$PWD"                   |     /home
48
-'$PWD'                   |     $PWD
49
-'\$PWD'                  |     \$PWD
50
-'"hello"'                |     "hello"
51
-he\$PWD                  |     he$PWD
52
-"he\$PWD"                |     he$PWD
53
-'he\$PWD'                |     he\$PWD
54
-he${PWD                  |     error
55
-he${PWD:=000}xx          |     error
56
-he${PWD:+${PWD}:}xx      |     he/home:xx
57
-he${XXX:-\$PWD:}xx       |     he$PWD:xx
58
-he${XXX:-\${PWD}z}xx     |     he${PWDz}xx
59
-안녕하세요                 |     안녕하세요
60
-안'녕'하세요               |     안녕하세요
61
-안'녕하세요                |     안녕하세요
62
-안녕\'하세요               |     안녕'하세요
63
-안\\'녕하세요              |     안\녕하세요
64
-안녕\t하세요               |     안녕t하세요
65
-"안녕\t하세요"             |     안녕\t하세요
66
-'안녕\t하세요              |     안녕\t하세요
67
-안녕하세요\                |     안녕하세요
68
-안녕하세요\\               |     안녕하세요\
69
-"안녕하세요                |     안녕하세요
70
-"안녕하세요\"              |     안녕하세요"
71
-"안녕'하세요"              |     안녕'하세요
72
-'안녕하세요                |     안녕하세요
73
-'안녕하세요\'              |     안녕하세요\
74
-안녕$1x                    |     안녕x
75
-안녕$.x                    |     안녕$.x
76
-안녕$pwd.                  |     안녕.
77
-안녕$PWD                   |     안녕/home
78
-안녕\$PWD                  |     안녕$PWD
79
-안녕\\$PWD                 |     안녕\/home
80
-안녕\${}                   |     안녕${}
81
-안녕\${}xx                 |     안녕${}xx
82
-안녕${}                    |     안녕
83
-안녕${}xx                  |     안녕xx
84
-안녕${hi}                  |     안녕
85
-안녕${hi}xx                |     안녕xx
86
-안녕${PWD}                 |     안녕/home
87
-안녕${.}                   |     error
88
-안녕${XXX:-000}xx          |     안녕000xx
89
-안녕${PWD:-000}xx          |     안녕/homexx
90
-안녕${XXX:-$PWD}xx         |     안녕/homexx
91
-안녕${XXX:-${PWD:-yyy}}xx  |     안녕/homexx
92
-안녕${XXX:-${YYY:-yyy}}xx  |     안녕yyyxx
93
-안녕${XXX:YYY}             |     error
94
-안녕${XXX:+${PWD}}xx       |     안녕xx
95
-안녕${PWD:+${XXX}}xx       |     안녕xx
96
-안녕${PWD:+${SHELL}}xx     |     안녕bashxx
97
-안녕${XXX:+000}xx          |     안녕xx
98
-안녕${PWD:+000}xx          |     안녕000xx
99
-'안녕${XX}'                |     안녕${XX}
100
-"안녕${PWD}"               |     안녕/home
101
-"안녕'$PWD'"               |     안녕'/home'
102
-'"안녕"'                   |     "안녕"
103
-안녕\$PWD                  |     안녕$PWD
104
-"안녕\$PWD"                |     안녕$PWD
105
-'안녕\$PWD'                |     안녕\$PWD
106
-안녕${PWD                  |     error
107
-안녕${PWD:=000}xx          |     error
108
-안녕${PWD:+${PWD}:}xx      |     안녕/home:xx
109
-안녕${XXX:-\$PWD:}xx       |     안녕$PWD:xx
110
-안녕${XXX:-\${PWD}z}xx     |     안녕${PWDz}xx
111
-$KOREAN                    |     한국어
112
-안녕$KOREAN                |     안녕한국어
1
+A|hello                    |     hello
2
+A|he'll'o                  |     hello
3
+A|he'llo                   |     hello
4
+A|he\'llo                  |     he'llo
5
+A|he\\'llo                 |     he\llo
6
+A|abc\tdef                 |     abctdef
7
+A|"abc\tdef"               |     abc\tdef
8
+A|'abc\tdef'               |     abc\tdef
9
+A|hello\                   |     hello
10
+A|hello\\                  |     hello\
11
+A|"hello                   |     hello
12
+A|"hello\"                 |     hello"
13
+A|"hel'lo"                 |     hel'lo
14
+A|'hello                   |     hello
15
+A|'hello\'                 |     hello\
16
+A|"''"                     |     ''
17
+A|$.                       |     $.
18
+A|$1                       |
19
+A|he$1x                    |     hex
20
+A|he$.x                    |     he$.x
21
+# Next one is different on Windows as $pwd==$PWD
22
+U|he$pwd.                  |     he.
23
+W|he$pwd.                  |     he/home.
24
+A|he$PWD                   |     he/home
25
+A|he\$PWD                  |     he$PWD
26
+A|he\\$PWD                 |     he\/home
27
+A|he\${}                   |     he${}
28
+A|he\${}xx                 |     he${}xx
29
+A|he${}                    |     he
30
+A|he${}xx                  |     hexx
31
+A|he${hi}                  |     he
32
+A|he${hi}xx                |     hexx
33
+A|he${PWD}                 |     he/home
34
+A|he${.}                   |     error
35
+A|he${XXX:-000}xx          |     he000xx
36
+A|he${PWD:-000}xx          |     he/homexx
37
+A|he${XXX:-$PWD}xx         |     he/homexx
38
+A|he${XXX:-${PWD:-yyy}}xx  |     he/homexx
39
+A|he${XXX:-${YYY:-yyy}}xx  |     heyyyxx
40
+A|he${XXX:YYY}             |     error
41
+A|he${XXX:+${PWD}}xx       |     hexx
42
+A|he${PWD:+${XXX}}xx       |     hexx
43
+A|he${PWD:+${SHELL}}xx     |     hebashxx
44
+A|he${XXX:+000}xx          |     hexx
45
+A|he${PWD:+000}xx          |     he000xx
46
+A|'he${XX}'                |     he${XX}
47
+A|"he${PWD}"               |     he/home
48
+A|"he'$PWD'"               |     he'/home'
49
+A|"$PWD"                   |     /home
50
+A|'$PWD'                   |     $PWD
51
+A|'\$PWD'                  |     \$PWD
52
+A|'"hello"'                |     "hello"
53
+A|he\$PWD                  |     he$PWD
54
+A|"he\$PWD"                |     he$PWD
55
+A|'he\$PWD'                |     he\$PWD
56
+A|he${PWD                  |     error
57
+A|he${PWD:=000}xx          |     error
58
+A|he${PWD:+${PWD}:}xx      |     he/home:xx
59
+A|he${XXX:-\$PWD:}xx       |     he$PWD:xx
60
+A|he${XXX:-\${PWD}z}xx     |     he${PWDz}xx
61
+A|안녕하세요                 |     안녕하세요
62
+A|안'녕'하세요               |     안녕하세요
63
+A|안'녕하세요                |     안녕하세요
64
+A|안녕\'하세요               |     안녕'하세요
65
+A|안\\'녕하세요              |     안\녕하세요
66
+A|안녕\t하세요               |     안녕t하세요
67
+A|"안녕\t하세요"             |     안녕\t하세요
68
+A|'안녕\t하세요              |     안녕\t하세요
69
+A|안녕하세요\                |     안녕하세요
70
+A|안녕하세요\\               |     안녕하세요\
71
+A|"안녕하세요                |     안녕하세요
72
+A|"안녕하세요\"              |     안녕하세요"
73
+A|"안녕'하세요"              |     안녕'하세요
74
+A|'안녕하세요                |     안녕하세요
75
+A|'안녕하세요\'              |     안녕하세요\
76
+A|안녕$1x                    |     안녕x
77
+A|안녕$.x                    |     안녕$.x
78
+# Next one is different on Windows as $pwd==$PWD
79
+U|안녕$pwd.                  |     안녕.
80
+W|안녕$pwd.                  |     안녕/home.
81
+A|안녕$PWD                   |     안녕/home
82
+A|안녕\$PWD                  |     안녕$PWD
83
+A|안녕\\$PWD                 |     안녕\/home
84
+A|안녕\${}                   |     안녕${}
85
+A|안녕\${}xx                 |     안녕${}xx
86
+A|안녕${}                    |     안녕
87
+A|안녕${}xx                  |     안녕xx
88
+A|안녕${hi}                  |     안녕
89
+A|안녕${hi}xx                |     안녕xx
90
+A|안녕${PWD}                 |     안녕/home
91
+A|안녕${.}                   |     error
92
+A|안녕${XXX:-000}xx          |     안녕000xx
93
+A|안녕${PWD:-000}xx          |     안녕/homexx
94
+A|안녕${XXX:-$PWD}xx         |     안녕/homexx
95
+A|안녕${XXX:-${PWD:-yyy}}xx  |     안녕/homexx
96
+A|안녕${XXX:-${YYY:-yyy}}xx  |     안녕yyyxx
97
+A|안녕${XXX:YYY}             |     error
98
+A|안녕${XXX:+${PWD}}xx       |     안녕xx
99
+A|안녕${PWD:+${XXX}}xx       |     안녕xx
100
+A|안녕${PWD:+${SHELL}}xx     |     안녕bashxx
101
+A|안녕${XXX:+000}xx          |     안녕xx
102
+A|안녕${PWD:+000}xx          |     안녕000xx
103
+A|'안녕${XX}'                |     안녕${XX}
104
+A|"안녕${PWD}"               |     안녕/home
105
+A|"안녕'$PWD'"               |     안녕'/home'
106
+A|'"안녕"'                   |     "안녕"
107
+A|안녕\$PWD                  |     안녕$PWD
108
+A|"안녕\$PWD"                |     안녕$PWD
109
+A|'안녕\$PWD'                |     안녕\$PWD
110
+A|안녕${PWD                  |     error
111
+A|안녕${PWD:=000}xx          |     error
112
+A|안녕${PWD:+${PWD}:}xx      |     안녕/home:xx
113
+A|안녕${XXX:-\$PWD:}xx       |     안녕$PWD:xx
114
+A|안녕${XXX:-\${PWD}z}xx     |     안녕${PWDz}xx
115
+A|$KOREAN                    |     한국어
116
+A|안녕$KOREAN                |     안녕한국어
... ...
@@ -8,6 +8,7 @@ package dockerfile
8 8
 
9 9
 import (
10 10
 	"fmt"
11
+	"runtime"
11 12
 	"strings"
12 13
 	"text/scanner"
13 14
 	"unicode"
... ...
@@ -298,9 +299,16 @@ func (sw *shellWord) processName() string {
298 298
 }
299 299
 
300 300
 func (sw *shellWord) getEnv(name string) string {
301
+	if runtime.GOOS == "windows" {
302
+		// Case-insensitive environment variables on Windows
303
+		name = strings.ToUpper(name)
304
+	}
301 305
 	for _, env := range sw.envs {
302 306
 		i := strings.Index(env, "=")
303 307
 		if i < 0 {
308
+			if runtime.GOOS == "windows" {
309
+				env = strings.ToUpper(env)
310
+			}
304 311
 			if name == env {
305 312
 				// Should probably never get here, but just in case treat
306 313
 				// it like "var" and "var=" are the same
... ...
@@ -308,7 +316,11 @@ func (sw *shellWord) getEnv(name string) string {
308 308
 			}
309 309
 			continue
310 310
 		}
311
-		if name != env[:i] {
311
+		compareName := env[:i]
312
+		if runtime.GOOS == "windows" {
313
+			compareName = strings.ToUpper(compareName)
314
+		}
315
+		if name != compareName {
312 316
 			continue
313 317
 		}
314 318
 		return env[i+1:]
... ...
@@ -3,12 +3,14 @@ package dockerfile
3 3
 import (
4 4
 	"bufio"
5 5
 	"os"
6
+	"runtime"
6 7
 	"strings"
7 8
 	"testing"
8 9
 )
9 10
 
10 11
 func TestShellParser4EnvVars(t *testing.T) {
11 12
 	fn := "envVarTest"
13
+	lineCount := 0
12 14
 
13 15
 	file, err := os.Open(fn)
14 16
 	if err != nil {
... ...
@@ -20,6 +22,7 @@ func TestShellParser4EnvVars(t *testing.T) {
20 20
 	envs := []string{"PWD=/home", "SHELL=bash", "KOREAN=한국어"}
21 21
 	for scanner.Scan() {
22 22
 		line := scanner.Text()
23
+		lineCount++
23 24
 
24 25
 		// Trim comments and blank lines
25 26
 		i := strings.Index(line, "#")
... ...
@@ -33,21 +36,30 @@ func TestShellParser4EnvVars(t *testing.T) {
33 33
 		}
34 34
 
35 35
 		words := strings.Split(line, "|")
36
-		if len(words) != 2 {
36
+		if len(words) != 3 {
37 37
 			t.Fatalf("Error in '%s' - should be exactly one | in:%q", fn, line)
38 38
 		}
39 39
 
40 40
 		words[0] = strings.TrimSpace(words[0])
41 41
 		words[1] = strings.TrimSpace(words[1])
42
+		words[2] = strings.TrimSpace(words[2])
42 43
 
43
-		newWord, err := ProcessWord(words[0], envs, '\\')
44
-
45
-		if err != nil {
46
-			newWord = "error"
44
+		// Key W=Windows; A=All; U=Unix
45
+		if (words[0] != "W") && (words[0] != "A") && (words[0] != "U") {
46
+			t.Fatalf("Invalid tag %s at line %d of %s. Must be W, A or U", words[0], lineCount, fn)
47 47
 		}
48 48
 
49
-		if newWord != words[1] {
50
-			t.Fatalf("Error. Src: %s  Calc: %s  Expected: %s", words[0], newWord, words[1])
49
+		if ((words[0] == "W" || words[0] == "A") && runtime.GOOS == "windows") ||
50
+			((words[0] == "U" || words[0] == "A") && runtime.GOOS != "windows") {
51
+			newWord, err := ProcessWord(words[1], envs, '\\')
52
+
53
+			if err != nil {
54
+				newWord = "error"
55
+			}
56
+
57
+			if newWord != words[2] {
58
+				t.Fatalf("Error. Src: %s  Calc: %s  Expected: %s at line %d", words[1], newWord, words[2], lineCount)
59
+			}
51 60
 		}
52 61
 	}
53 62
 }
... ...
@@ -17,7 +17,6 @@ import (
17 17
 
18 18
 type psOptions struct {
19 19
 	nodeIDs   []string
20
-	all       bool
21 20
 	noResolve bool
22 21
 	noTrunc   bool
23 22
 	filter    opts.FilterOpt
... ...
@@ -44,7 +43,6 @@ func newPsCommand(dockerCli *command.DockerCli) *cobra.Command {
44 44
 	flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
45 45
 	flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
46 46
 	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
47
-	flags.BoolVarP(&opts.all, "all", "a", false, "Show all tasks (default shows tasks that are or will be running)")
48 47
 
49 48
 	return cmd
50 49
 }
... ...
@@ -74,11 +72,6 @@ func runPs(dockerCli *command.DockerCli, opts psOptions) error {
74 74
 		filter := opts.filter.Value()
75 75
 		filter.Add("node", node.ID)
76 76
 
77
-		if !opts.all && !filter.Include("desired-state") {
78
-			filter.Add("desired-state", string(swarm.TaskStateRunning))
79
-			filter.Add("desired-state", string(swarm.TaskStateAccepted))
80
-		}
81
-
82 77
 		nodeTasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
83 78
 		if err != nil {
84 79
 			errs = append(errs, err.Error())
... ...
@@ -1,12 +1,9 @@
1 1
 package plugin
2 2
 
3 3
 import (
4
-	"fmt"
5
-
6 4
 	"github.com/docker/docker/cli"
7 5
 	"github.com/docker/docker/cli/command"
8 6
 	"github.com/docker/docker/cli/command/inspect"
9
-	"github.com/docker/docker/reference"
10 7
 	"github.com/spf13/cobra"
11 8
 	"golang.org/x/net/context"
12 9
 )
... ...
@@ -20,7 +17,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
20 20
 	var opts inspectOptions
21 21
 
22 22
 	cmd := &cobra.Command{
23
-		Use:   "inspect [OPTIONS] PLUGIN [PLUGIN...]",
23
+		Use:   "inspect [OPTIONS] PLUGIN|ID [PLUGIN|ID...]",
24 24
 		Short: "Display detailed information on one or more plugins",
25 25
 		Args:  cli.RequiresMinArgs(1),
26 26
 		RunE: func(cmd *cobra.Command, args []string) error {
... ...
@@ -37,20 +34,8 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
37 37
 func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
38 38
 	client := dockerCli.Client()
39 39
 	ctx := context.Background()
40
-	getRef := func(name string) (interface{}, []byte, error) {
41
-		named, err := reference.ParseNamed(name) // FIXME: validate
42
-		if err != nil {
43
-			return nil, nil, err
44
-		}
45
-		if reference.IsNameOnly(named) {
46
-			named = reference.WithDefaultTag(named)
47
-		}
48
-		ref, ok := named.(reference.NamedTagged)
49
-		if !ok {
50
-			return nil, nil, fmt.Errorf("invalid name: %s", named.String())
51
-		}
52
-
53
-		return client.PluginInspectWithRaw(ctx, ref.String())
40
+	getRef := func(ref string) (interface{}, []byte, error) {
41
+		return client.PluginInspectWithRaw(ctx, ref)
54 42
 	}
55 43
 
56 44
 	return inspect.Inspect(dockerCli.Out(), opts.pluginNames, opts.format, getRef)
... ...
@@ -25,9 +25,9 @@ func newSecretCreateCommand(dockerCli *command.DockerCli) *cobra.Command {
25 25
 	}
26 26
 
27 27
 	cmd := &cobra.Command{
28
-		Use:   "create [OPTIONS] SECRET [SECRET...]",
28
+		Use:   "create [OPTIONS] SECRET",
29 29
 		Short: "Create a secret using stdin as content",
30
-		Args:  cli.RequiresMinArgs(1),
30
+		Args:  cli.ExactArgs(1),
31 31
 		RunE: func(cmd *cobra.Command, args []string) error {
32 32
 			createOpts.name = args[0]
33 33
 			return runSecretCreate(dockerCli, createOpts)
... ...
@@ -25,7 +25,7 @@ func newSecretInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
25 25
 		},
26 26
 	}
27 27
 
28
-	cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given go template")
28
+	cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
29 29
 	return cmd
30 30
 }
31 31
 
... ...
@@ -21,9 +21,10 @@ func newSecretListCommand(dockerCli *command.DockerCli) *cobra.Command {
21 21
 	opts := listOptions{}
22 22
 
23 23
 	cmd := &cobra.Command{
24
-		Use:   "ls [OPTIONS]",
25
-		Short: "List secrets",
26
-		Args:  cli.NoArgs,
24
+		Use:     "ls [OPTIONS]",
25
+		Aliases: []string{"list"},
26
+		Short:   "List secrets",
27
+		Args:    cli.NoArgs,
27 28
 		RunE: func(cmd *cobra.Command, args []string) error {
28 29
 			return runSecretList(dockerCli, opts)
29 30
 		},
... ...
@@ -16,9 +16,10 @@ type removeOptions struct {
16 16
 
17 17
 func newSecretRemoveCommand(dockerCli *command.DockerCli) *cobra.Command {
18 18
 	return &cobra.Command{
19
-		Use:   "rm SECRET [SECRET...]",
20
-		Short: "Remove one or more secrets",
21
-		Args:  cli.RequiresMinArgs(1),
19
+		Use:     "rm SECRET [SECRET...]",
20
+		Aliases: []string{"remove"},
21
+		Short:   "Remove one or more secrets",
22
+		Args:    cli.RequiresMinArgs(1),
22 23
 		RunE: func(cmd *cobra.Command, args []string) error {
23 24
 			opts := removeOptions{
24 25
 				names: args,
... ...
@@ -2,7 +2,6 @@ package service
2 2
 
3 3
 import (
4 4
 	"github.com/docker/docker/api/types"
5
-	"github.com/docker/docker/api/types/swarm"
6 5
 	"github.com/docker/docker/cli"
7 6
 	"github.com/docker/docker/cli/command"
8 7
 	"github.com/docker/docker/cli/command/idresolver"
... ...
@@ -15,7 +14,6 @@ import (
15 15
 
16 16
 type psOptions struct {
17 17
 	serviceID string
18
-	all       bool
19 18
 	quiet     bool
20 19
 	noResolve bool
21 20
 	noTrunc   bool
... ...
@@ -39,7 +37,6 @@ func newPsCommand(dockerCli *command.DockerCli) *cobra.Command {
39 39
 	flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
40 40
 	flags.BoolVar(&opts.noResolve, "no-resolve", false, "Do not map IDs to Names")
41 41
 	flags.VarP(&opts.filter, "filter", "f", "Filter output based on conditions provided")
42
-	flags.BoolVarP(&opts.all, "all", "a", false, "Show all tasks (default shows tasks that are or will be running)")
43 42
 
44 43
 	return cmd
45 44
 }
... ...
@@ -67,11 +64,6 @@ func runPS(dockerCli *command.DockerCli, opts psOptions) error {
67 67
 		}
68 68
 	}
69 69
 
70
-	if !opts.all && !filter.Include("desired-state") {
71
-		filter.Add("desired-state", string(swarm.TaskStateRunning))
72
-		filter.Add("desired-state", string(swarm.TaskStateAccepted))
73
-	}
74
-
75 70
 	tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
76 71
 	if err != nil {
77 72
 		return err
... ...
@@ -37,7 +37,7 @@ func getServices(
37 37
 		types.ServiceListOptions{Filters: getStackFilter(namespace)})
38 38
 }
39 39
 
40
-func getNetworks(
40
+func getStackNetworks(
41 41
 	ctx context.Context,
42 42
 	apiclient client.APIClient,
43 43
 	namespace string,
... ...
@@ -22,6 +22,7 @@ import (
22 22
 	"github.com/docker/docker/cli"
23 23
 	"github.com/docker/docker/cli/command"
24 24
 	servicecmd "github.com/docker/docker/cli/command/service"
25
+	dockerclient "github.com/docker/docker/client"
25 26
 	"github.com/docker/docker/opts"
26 27
 	runconfigopts "github.com/docker/docker/runconfig/opts"
27 28
 	"github.com/docker/go-connections/nat"
... ...
@@ -123,7 +124,10 @@ func deployCompose(ctx context.Context, dockerCli *command.DockerCli, opts deplo
123 123
 
124 124
 	namespace := namespace{name: opts.namespace}
125 125
 
126
-	networks := convertNetworks(namespace, config.Networks)
126
+	networks, externalNetworks := convertNetworks(namespace, config.Networks)
127
+	if err := validateExternalNetworks(ctx, dockerCli, externalNetworks); err != nil {
128
+		return err
129
+	}
127 130
 	if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil {
128 131
 		return err
129 132
 	}
... ...
@@ -179,7 +183,7 @@ func getConfigFile(filename string) (*composetypes.ConfigFile, error) {
179 179
 func convertNetworks(
180 180
 	namespace namespace,
181 181
 	networks map[string]composetypes.NetworkConfig,
182
-) map[string]types.NetworkCreate {
182
+) (map[string]types.NetworkCreate, []string) {
183 183
 	if networks == nil {
184 184
 		networks = make(map[string]composetypes.NetworkConfig)
185 185
 	}
... ...
@@ -187,10 +191,12 @@ func convertNetworks(
187 187
 	// TODO: only add default network if it's used
188 188
 	networks["default"] = composetypes.NetworkConfig{}
189 189
 
190
+	externalNetworks := []string{}
190 191
 	result := make(map[string]types.NetworkCreate)
191 192
 
192 193
 	for internalName, network := range networks {
193
-		if network.External.Name != "" {
194
+		if network.External.External {
195
+			externalNetworks = append(externalNetworks, network.External.Name)
194 196
 			continue
195 197
 		}
196 198
 
... ...
@@ -216,7 +222,29 @@ func convertNetworks(
216 216
 		result[internalName] = createOpts
217 217
 	}
218 218
 
219
-	return result
219
+	return result, externalNetworks
220
+}
221
+
222
+func validateExternalNetworks(
223
+	ctx context.Context,
224
+	dockerCli *command.DockerCli,
225
+	externalNetworks []string) error {
226
+	client := dockerCli.Client()
227
+
228
+	for _, networkName := range externalNetworks {
229
+		network, err := client.NetworkInspect(ctx, networkName)
230
+		if err != nil {
231
+			if dockerclient.IsErrNetworkNotFound(err) {
232
+				return fmt.Errorf("network %q is declared as external, but could not be found. You need to create the network before the stack is deployed (with overlay driver)", networkName)
233
+			}
234
+			return err
235
+		}
236
+		if network.Scope != "swarm" {
237
+			return fmt.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of %q", networkName, network.Scope, "swarm")
238
+		}
239
+	}
240
+
241
+	return nil
220 242
 }
221 243
 
222 244
 func createNetworks(
... ...
@@ -227,7 +255,7 @@ func createNetworks(
227 227
 ) error {
228 228
 	client := dockerCli.Client()
229 229
 
230
-	existingNetworks, err := getNetworks(ctx, client, namespace.name)
230
+	existingNetworks, err := getStackNetworks(ctx, client, namespace.name)
231 231
 	if err != nil {
232 232
 		return err
233 233
 	}
... ...
@@ -258,30 +286,39 @@ func createNetworks(
258 258
 
259 259
 func convertServiceNetworks(
260 260
 	networks map[string]*composetypes.ServiceNetworkConfig,
261
+	networkConfigs map[string]composetypes.NetworkConfig,
261 262
 	namespace namespace,
262 263
 	name string,
263
-) []swarm.NetworkAttachmentConfig {
264
+) ([]swarm.NetworkAttachmentConfig, error) {
264 265
 	if len(networks) == 0 {
265 266
 		return []swarm.NetworkAttachmentConfig{
266 267
 			{
267 268
 				Target:  namespace.scope("default"),
268 269
 				Aliases: []string{name},
269 270
 			},
270
-		}
271
+		}, nil
271 272
 	}
272 273
 
273 274
 	nets := []swarm.NetworkAttachmentConfig{}
274 275
 	for networkName, network := range networks {
276
+		networkConfig, ok := networkConfigs[networkName]
277
+		if !ok {
278
+			return []swarm.NetworkAttachmentConfig{}, fmt.Errorf("invalid network: %s", networkName)
279
+		}
275 280
 		var aliases []string
276 281
 		if network != nil {
277 282
 			aliases = network.Aliases
278 283
 		}
284
+		target := namespace.scope(networkName)
285
+		if networkConfig.External.External {
286
+			target = networkName
287
+		}
279 288
 		nets = append(nets, swarm.NetworkAttachmentConfig{
280
-			Target:  namespace.scope(networkName),
289
+			Target:  target,
281 290
 			Aliases: append(aliases, name),
282 291
 		})
283 292
 	}
284
-	return nets
293
+	return nets, nil
285 294
 }
286 295
 
287 296
 func convertVolumes(
... ...
@@ -472,9 +509,10 @@ func convertServices(
472 472
 
473 473
 	services := config.Services
474 474
 	volumes := config.Volumes
475
+	networks := config.Networks
475 476
 
476 477
 	for _, service := range services {
477
-		serviceSpec, err := convertService(namespace, service, volumes)
478
+		serviceSpec, err := convertService(namespace, service, networks, volumes)
478 479
 		if err != nil {
479 480
 			return nil, err
480 481
 		}
... ...
@@ -487,6 +525,7 @@ func convertServices(
487 487
 func convertService(
488 488
 	namespace namespace,
489 489
 	service composetypes.ServiceConfig,
490
+	networkConfigs map[string]composetypes.NetworkConfig,
490 491
 	volumes map[string]composetypes.VolumeConfig,
491 492
 ) (swarm.ServiceSpec, error) {
492 493
 	name := namespace.scope(service.Name)
... ...
@@ -523,6 +562,11 @@ func convertService(
523 523
 		return swarm.ServiceSpec{}, err
524 524
 	}
525 525
 
526
+	networks, err := convertServiceNetworks(service.Networks, networkConfigs, namespace, service.Name)
527
+	if err != nil {
528
+		return swarm.ServiceSpec{}, err
529
+	}
530
+
526 531
 	serviceSpec := swarm.ServiceSpec{
527 532
 		Annotations: swarm.Annotations{
528 533
 			Name:   name,
... ...
@@ -553,7 +597,7 @@ func convertService(
553 553
 		},
554 554
 		EndpointSpec: endpoint,
555 555
 		Mode:         mode,
556
-		Networks:     convertServiceNetworks(service.Networks, namespace, service.Name),
556
+		Networks:     networks,
557 557
 		UpdateConfig: convertUpdateConfig(service.Deploy.UpdateConfig),
558 558
 	}
559 559
 
... ...
@@ -49,7 +49,7 @@ func runRemove(dockerCli *command.DockerCli, opts removeOptions) error {
49 49
 		}
50 50
 	}
51 51
 
52
-	networks, err := getNetworks(ctx, client, namespace)
52
+	networks, err := getStackNetworks(ctx, client, namespace)
53 53
 	if err != nil {
54 54
 		return err
55 55
 	}
... ...
@@ -135,7 +135,7 @@ func (c *Config) CopyToPipe(iop libcontainerd.IOPipe) {
135 135
 			go func() {
136 136
 				pools.Copy(iop.Stdin, stdin)
137 137
 				if err := iop.Stdin.Close(); err != nil {
138
-					logrus.Errorf("failed to close stdin: %+v", err)
138
+					logrus.Warnf("failed to close stdin: %+v", err)
139 139
 				}
140 140
 			}()
141 141
 		}
... ...
@@ -123,7 +123,7 @@ __docker_complete_container_ids() {
123 123
 	COMPREPLY=( $(compgen -W "${containers[*]}" -- "$cur") )
124 124
 }
125 125
 
126
-__docker_complete_images() {
126
+__docker_images() {
127 127
 	local images_args=""
128 128
 
129 129
 	case "$DOCKER_COMPLETION_SHOW_IMAGE_IDS" in
... ...
@@ -152,8 +152,11 @@ __docker_complete_images() {
152 152
 			;;
153 153
 	esac
154 154
 
155
-	local images=$(__docker_q images $images_args | awk "$awk_script")
156
-	COMPREPLY=( $(compgen -W "$images" -- "$cur") )
155
+	__docker_q images $images_args | awk "$awk_script" | grep -v '<none>$'
156
+}
157
+
158
+__docker_complete_images() {
159
+	COMPREPLY=( $(compgen -W "$(__docker_images)" -- "$cur") )
157 160
 	__ltrim_colon_completions "$cur"
158 161
 }
159 162
 
... ...
@@ -168,13 +171,6 @@ __docker_complete_image_repos_and_tags() {
168 168
 	__ltrim_colon_completions "$cur"
169 169
 }
170 170
 
171
-__docker_complete_containers_and_images() {
172
-	__docker_complete_containers_all
173
-	local containers=( "${COMPREPLY[@]}" )
174
-	__docker_complete_images
175
-	COMPREPLY+=( "${containers[@]}" )
176
-}
177
-
178 171
 # __docker_networks returns a list of all networks. Additional options to
179 172
 # `docker network ls` may be specified in order to filter the list, e.g.
180 173
 # `__docker_networks --filter type=custom`
... ...
@@ -315,6 +311,22 @@ __docker_complete_runtimes() {
315 315
 	COMPREPLY=( $(compgen -W "$(__docker_runtimes)" -- "$cur") )
316 316
 }
317 317
 
318
+# __docker_stacks returns a list of all stacks.
319
+__docker_stacks() {
320
+	__docker_q stack ls | awk 'NR>1 {print $1}'
321
+}
322
+
323
+# __docker_complete_stacks applies completion of stacks based on the current value
324
+# of `$cur` or the value of the optional first option `--cur`, if given.
325
+__docker_complete_stacks() {
326
+	local current="$cur"
327
+	if [ "$1" = "--cur" ] ; then
328
+		current="$2"
329
+		shift 2
330
+	fi
331
+	COMPREPLY=( $(compgen -W "$(__docker_stacks "$@")" -- "$current") )
332
+}
333
+
318 334
 # __docker_nodes returns a list of all nodes. Additional options to
319 335
 # `docker node ls` may be specified in order to filter the list, e.g.
320 336
 # `__docker_nodes --filter role=manager`
... ...
@@ -397,6 +409,13 @@ __docker_append_to_completions() {
397 397
 	COMPREPLY=( ${COMPREPLY[@]/%/"$1"} )
398 398
 }
399 399
 
400
+# __docker_is_experimental tests whether the currently configured Docker daemon
401
+# runs in experimental mode. If so, the function exits with 0 (true).
402
+# Otherwise, or if the result cannot be determined, the exit value is 1 (false).
403
+__docker_is_experimental() {
404
+	[ "$(__docker_q version -f '{{.Server.Experimental}}')" = "true" ]
405
+}
406
+
400 407
 # __docker_pos_first_nonflag finds the position of the first word that is neither
401 408
 # option nor an option's argument. If there are options that require arguments,
402 409
 # you should pass a glob describing those options, e.g. "--option1|-o|--option2"
... ...
@@ -839,6 +858,7 @@ _docker_docker() {
839 839
 		*)
840 840
 			local counter=$( __docker_pos_first_nonflag "$(__docker_to_extglob "$global_options_with_args")" )
841 841
 			if [ $cword -eq $counter ]; then
842
+				__docker_is_experimental && commands+=(${experimental_commands[*]})
842 843
 				COMPREPLY=( $( compgen -W "${commands[*]} help" -- "$cur" ) )
843 844
 			fi
844 845
 			;;
... ...
@@ -1297,7 +1317,6 @@ _docker_container_run() {
1297 1297
 		--memory-swap
1298 1298
 		--memory-swappiness
1299 1299
 		--memory-reservation
1300
-		--mount
1301 1300
 		--name
1302 1301
 		--network
1303 1302
 		--network-alias
... ...
@@ -1863,6 +1882,10 @@ _docker_daemon() {
1863 1863
 	esac
1864 1864
 }
1865 1865
 
1866
+_docker_deploy() {
1867
+	__docker_is_experimental && _docker_stack_deploy
1868
+}
1869
+
1866 1870
 _docker_diff() {
1867 1871
 	_docker_container_diff
1868 1872
 }
... ...
@@ -2253,7 +2276,7 @@ _docker_inspect() {
2253 2253
 			;;
2254 2254
 		--type)
2255 2255
 			if [ -z "$preselected_type" ] ; then
2256
-				COMPREPLY=( $( compgen -W "image container" -- "$cur" ) )
2256
+				COMPREPLY=( $( compgen -W "container image network node service volume" -- "$cur" ) )
2257 2257
 				return
2258 2258
 			fi
2259 2259
 			;;
... ...
@@ -2270,7 +2293,14 @@ _docker_inspect() {
2270 2270
 		*)
2271 2271
 			case "$type" in
2272 2272
 				'')
2273
-					__docker_complete_containers_and_images
2273
+					COMPREPLY=( $( compgen -W "
2274
+						$(__docker_containers --all)
2275
+						$(__docker_images)
2276
+						$(__docker_networks)
2277
+						$(__docker_nodes)
2278
+						$(__docker_services)
2279
+						$(__docker_volumes)
2280
+					" -- "$cur" ) )
2274 2281
 					;;
2275 2282
 				container)
2276 2283
 					__docker_complete_containers_all
... ...
@@ -2278,6 +2308,18 @@ _docker_inspect() {
2278 2278
 				image)
2279 2279
 					__docker_complete_images
2280 2280
 					;;
2281
+				network)
2282
+					__docker_complete_networks
2283
+					;;
2284
+				node)
2285
+					__docker_complete_nodes
2286
+					;;
2287
+				service)
2288
+					__docker_complete_services
2289
+					;;
2290
+				volume)
2291
+					__docker_complete_volumes
2292
+					;;
2281 2293
 			esac
2282 2294
 	esac
2283 2295
 }
... ...
@@ -2623,7 +2665,7 @@ _docker_service_ps() {
2623 2623
 
2624 2624
 	case "$cur" in
2625 2625
 		-*)
2626
-			COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-resolve --no-trunc --quiet -q" -- "$cur" ) )
2626
+			COMPREPLY=( $( compgen -W "--filter -f --help --no-resolve --no-trunc --quiet -q" -- "$cur" ) )
2627 2627
 			;;
2628 2628
 		*)
2629 2629
 			local counter=$(__docker_pos_first_nonflag '--filter|-f')
... ...
@@ -3318,6 +3360,166 @@ _docker_search() {
3318 3318
 	esac
3319 3319
 }
3320 3320
 
3321
+
3322
+_docker_stack() {
3323
+	local subcommands="
3324
+		deploy
3325
+		ls
3326
+		ps
3327
+		rm
3328
+		services
3329
+	"
3330
+	local aliases="
3331
+		down
3332
+		list
3333
+		remove
3334
+		up
3335
+	"
3336
+	__docker_subcommands "$subcommands $aliases" && return
3337
+
3338
+	case "$cur" in
3339
+		-*)
3340
+			COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
3341
+			;;
3342
+		*)
3343
+			COMPREPLY=( $( compgen -W "$subcommands" -- "$cur" ) )
3344
+			;;
3345
+	esac
3346
+}
3347
+
3348
+_docker_stack_deploy() {
3349
+	case "$prev" in
3350
+		--bundle-file)
3351
+			_filedir dab
3352
+			return
3353
+			;;
3354
+		--compose-file|-c)
3355
+			_filedir yml
3356
+			return
3357
+			;;
3358
+	esac
3359
+
3360
+	case "$cur" in
3361
+		-*)
3362
+			COMPREPLY=( $( compgen -W "--bundle-file --compose-file -c --help --with-registry-auth" -- "$cur" ) )
3363
+			;;
3364
+	esac
3365
+}
3366
+
3367
+_docker_stack_down() {
3368
+	_docker_stack_rm
3369
+}
3370
+
3371
+_docker_stack_list() {
3372
+	_docker_stack_ls
3373
+}
3374
+
3375
+_docker_stack_ls() {
3376
+	case "$cur" in
3377
+		-*)
3378
+			COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
3379
+			;;
3380
+	esac
3381
+}
3382
+
3383
+_docker_stack_ps() {
3384
+	local key=$(__docker_map_key_of_current_option '--filter|-f')
3385
+	case "$key" in
3386
+		desired-state)
3387
+			COMPREPLY=( $( compgen -W "accepted running" -- "${cur##*=}" ) )
3388
+			return
3389
+			;;
3390
+		id)
3391
+			__docker_complete_stacks --cur "${cur##*=}" --id
3392
+			return
3393
+			;;
3394
+		name)
3395
+			__docker_complete_stacks --cur "${cur##*=}" --name
3396
+			return
3397
+			;;
3398
+	esac
3399
+
3400
+	case "$prev" in
3401
+		--filter|-f)
3402
+			COMPREPLY=( $( compgen -S = -W "id name desired-state" -- "$cur" ) )
3403
+			__docker_nospace
3404
+			return
3405
+			;;
3406
+	esac
3407
+
3408
+	case "$cur" in
3409
+		-*)
3410
+			COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-resolve --no-trunc" -- "$cur" ) )
3411
+			;;
3412
+		*)
3413
+			local counter=$(__docker_pos_first_nonflag '--filter|-f')
3414
+			if [ $cword -eq $counter ]; then
3415
+				__docker_complete_stacks
3416
+			fi
3417
+			;;
3418
+	esac
3419
+}
3420
+
3421
+_docker_stack_remove() {
3422
+	_docker_stack_rm
3423
+}
3424
+
3425
+_docker_stack_rm() {
3426
+	case "$cur" in
3427
+		-*)
3428
+			COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
3429
+			;;
3430
+		*)
3431
+			local counter=$(__docker_pos_first_nonflag)
3432
+			if [ $cword -eq $counter ]; then
3433
+				__docker_complete_stacks
3434
+			fi
3435
+			;;
3436
+	esac
3437
+}
3438
+
3439
+_docker_stack_services() {
3440
+	local key=$(__docker_map_key_of_current_option '--filter|-f')
3441
+	case "$key" in
3442
+		id)
3443
+			__docker_complete_services --cur "${cur##*=}" --id
3444
+			return
3445
+			;;
3446
+		label)
3447
+			return
3448
+			;;
3449
+		name)
3450
+			__docker_complete_services --cur "${cur##*=}" --name
3451
+			return
3452
+			;;
3453
+	esac
3454
+
3455
+	case "$prev" in
3456
+		--filter|-f)
3457
+			COMPREPLY=( $( compgen -S = -W "id label name" -- "$cur" ) )
3458
+			__docker_nospace
3459
+			return
3460
+			;;
3461
+	esac
3462
+
3463
+	case "$cur" in
3464
+		-*)
3465
+			COMPREPLY=( $( compgen -W "--filter -f --help --quiet -q" -- "$cur" ) )
3466
+			;;
3467
+		*)
3468
+			local counter=$(__docker_pos_first_nonflag '--filter|-f')
3469
+			if [ $cword -eq $counter ]; then
3470
+				__docker_complete_stacks
3471
+			fi
3472
+			;;
3473
+	esac
3474
+}
3475
+
3476
+_docker_stack_up() {
3477
+	_docker_stack_deploy
3478
+}
3479
+
3480
+
3321 3481
 _docker_start() {
3322 3482
 	_docker_container_start
3323 3483
 }
... ...
@@ -3651,6 +3853,7 @@ _docker() {
3651 3651
 		save
3652 3652
 		search
3653 3653
 		service
3654
+		stack
3654 3655
 		start
3655 3656
 		stats
3656 3657
 		stop
... ...
@@ -3665,6 +3868,10 @@ _docker() {
3665 3665
 		wait
3666 3666
 	)
3667 3667
 
3668
+	local experimental_commands=(
3669
+		deploy
3670
+	)
3671
+
3668 3672
 	# These options are valid as global options for all client commands
3669 3673
 	# and valid as command options for `docker daemon`
3670 3674
 	local global_boolean_options="
... ...
@@ -137,7 +137,6 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l link -d 'Add
137 137
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -s m -l memory -d 'Memory limit (format: <number>[<unit>], where unit = b, k, m or g)'
138 138
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l mac-address -d 'Container MAC address (e.g. 92:d0:c6:0a:29:33)'
139 139
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l memory-swap -d "Total memory usage (memory + swap), set '-1' to disable swap (format: <number>[<unit>], where unit = b, k, m or g)"
140
-complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l mount -d 'Attach a filesystem mount to the container'
141 140
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l name -d 'Assign a name to the container'
142 141
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l net -d 'Set the Network mode for the container'
143 142
 complete -c docker -A -f -n '__fish_seen_subcommand_from create' -s P -l publish-all -d 'Publish all exposed ports to random ports on the host interfaces'
... ...
@@ -329,7 +328,6 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l link -d 'Add li
329 329
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s m -l memory -d 'Memory limit (format: <number>[<unit>], where unit = b, k, m or g)'
330 330
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l mac-address -d 'Container MAC address (e.g. 92:d0:c6:0a:29:33)'
331 331
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l memory-swap -d "Total memory usage (memory + swap), set '-1' to disable swap (format: <number>[<unit>], where unit = b, k, m or g)"
332
-complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l mount -d 'Attach a filesystem mount to the container'
333 332
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l name -d 'Assign a name to the container'
334 333
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l net -d 'Set the Network mode for the container'
335 334
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s P -l publish-all -d 'Publish all exposed ports to random ports on the host interfaces'
... ...
@@ -551,7 +551,6 @@ __docker_container_subcommand() {
551 551
         "($help)--log-driver=[Default driver for container logs]:logging driver:__docker_complete_log_drivers"
552 552
         "($help)*--log-opt=[Log driver specific options]:log driver options:__docker_complete_log_options"
553 553
         "($help)--mac-address=[Container MAC address]:MAC address: "
554
-        "($help)*--mount=[Attach a filesystem mount to the container]:mount: "
555 554
         "($help)--name=[Container name]:name: "
556 555
         "($help)--network=[Connect a container to a network]:network mode:(bridge none container host)"
557 556
         "($help)*--network-alias=[Add network-scoped alias for the container]:alias: "
... ...
@@ -1883,6 +1882,147 @@ __docker_service_subcommand() {
1883 1883
 
1884 1884
 # EO service
1885 1885
 
1886
+# BO stack
1887
+
1888
+__docker_stack_complete_ps_filters() {
1889
+    [[ $PREFIX = -* ]] && return 1
1890
+    integer ret=1
1891
+
1892
+    if compset -P '*='; then
1893
+        case "${${words[-1]%=*}#*=}" in
1894
+            (desired-state)
1895
+                state_opts=('accepted' 'running')
1896
+                _describe -t state-opts "desired state options" state_opts && ret=0
1897
+                ;;
1898
+            *)
1899
+                _message 'value' && ret=0
1900
+                ;;
1901
+        esac
1902
+    else
1903
+        opts=('desired-state' 'id' 'name')
1904
+        _describe -t filter-opts "filter options" opts -qS "=" && ret=0
1905
+    fi
1906
+
1907
+    return ret
1908
+}
1909
+
1910
+__docker_stack_complete_services_filters() {
1911
+    [[ $PREFIX = -* ]] && return 1
1912
+    integer ret=1
1913
+
1914
+    if compset -P '*='; then
1915
+        case "${${words[-1]%=*}#*=}" in
1916
+            *)
1917
+                _message 'value' && ret=0
1918
+                ;;
1919
+        esac
1920
+    else
1921
+        opts=('id' 'label' 'name')
1922
+        _describe -t filter-opts "filter options" opts -qS "=" && ret=0
1923
+    fi
1924
+
1925
+    return ret
1926
+}
1927
+
1928
+__docker_stacks() {
1929
+    [[ $PREFIX = -* ]] && return 1
1930
+    integer ret=1
1931
+    local line s
1932
+    declare -a lines stacks
1933
+
1934
+    lines=(${(f)${:-"$(_call_program commands docker $docker_options stack ls)"$'\n'}})
1935
+
1936
+    # Parse header line to find columns
1937
+    local i=1 j=1 k header=${lines[1]}
1938
+    declare -A begin end
1939
+    while (( j < ${#header} - 1 )); do
1940
+        i=$(( j + ${${header[$j,-1]}[(i)[^ ]]} - 1 ))
1941
+        j=$(( i + ${${header[$i,-1]}[(i)  ]} - 1 ))
1942
+        k=$(( j + ${${header[$j,-1]}[(i)[^ ]]} - 2 ))
1943
+        begin[${header[$i,$((j-1))]}]=$i
1944
+        end[${header[$i,$((j-1))]}]=$k
1945
+    done
1946
+    end[${header[$i,$((j-1))]}]=-1
1947
+    lines=(${lines[2,-1]})
1948
+
1949
+    # Service ID
1950
+    for line in $lines; do
1951
+        s="${line[${begin[ID]},${end[ID]}]%% ##}"
1952
+        stacks=($stacks $s)
1953
+    done
1954
+
1955
+    _describe -t stacks-list "stacks" stacks "$@" && ret=0
1956
+    return ret
1957
+}
1958
+
1959
+__docker_complete_stacks() {
1960
+    [[ $PREFIX = -* ]] && return 1
1961
+    __docker_stacks "$@"
1962
+}
1963
+
1964
+__docker_stack_commands() {
1965
+    local -a _docker_stack_subcommands
1966
+    _docker_stack_subcommands=(
1967
+        "deploy:Deploy a new stack or update an existing stack"
1968
+        "ls:List stacks"
1969
+        "ps:List the tasks in the stack"
1970
+        "rm:Remove the stack"
1971
+        "services:List the services in the stack"
1972
+    )
1973
+    _describe -t docker-stack-commands "docker stack command" _docker_stack_subcommands
1974
+}
1975
+
1976
+__docker_stack_subcommand() {
1977
+    local -a _command_args opts_help
1978
+    local expl help="--help"
1979
+    integer ret=1
1980
+
1981
+    opts_help=("(: -)--help[Print usage]")
1982
+
1983
+    case "$words[1]" in
1984
+        (deploy|up)
1985
+            _arguments $(__docker_arguments) \
1986
+                $opts_help \
1987
+                "($help)--bundle-file=[Path to a Distributed Application Bundle file]:dab:_files -g \"*.dab\"" \
1988
+                "($help -c --compose-file)"{-c=,--compose-file=}"[Path to a Compose file]:compose file:_files -g \"*.(yml|yaml)\"" \
1989
+                "($help)--with-registry-auth[Send registry authentication details to Swarm agents]" \
1990
+                "($help -):stack:__docker_complete_stacks" && ret=0
1991
+            ;;
1992
+        (ls|list)
1993
+            _arguments $(__docker_arguments) \
1994
+                $opts_help && ret=0
1995
+            ;;
1996
+        (ps)
1997
+            _arguments $(__docker_arguments) \
1998
+                $opts_help \
1999
+                "($help -a --all)"{-a,--all}"[Display all tasks]" \
2000
+                "($help)*"{-f=,--filter=}"[Filter output based on conditions provided]:filter:__docker_stack_complete_ps_filters" \
2001
+                "($help)--no-resolve[Do not map IDs to Names]" \
2002
+                "($help)--no-trunc[Do not truncate output]" \
2003
+                "($help -):stack:__docker_complete_stacks" && ret=0
2004
+            ;;
2005
+        (rm|remove|down)
2006
+            _arguments $(__docker_arguments) \
2007
+                $opts_help \
2008
+                "($help -):stack:__docker_complete_stacks" && ret=0
2009
+            ;;
2010
+        (services)
2011
+            _arguments $(__docker_arguments) \
2012
+                $opts_help \
2013
+                "($help)*"{-f=,--filter=}"[Filter output based on conditions provided]:filter:__docker_stack_complete_services_filters" \
2014
+                "($help -q --quiet)"{-q,--quiet}"[Only display IDs]" \
2015
+                "($help -):stack:__docker_complete_stacks" && ret=0
2016
+            ;;
2017
+        (help)
2018
+            _arguments $(__docker_arguments) ":subcommand:__docker_stack_commands" && ret=0
2019
+            ;;
2020
+    esac
2021
+
2022
+    return ret
2023
+}
2024
+
2025
+# EO stack
2026
+
1886 2027
 # BO swarm
1887 2028
 
1888 2029
 __docker_swarm_commands() {
... ...
@@ -2451,6 +2591,23 @@ __docker_subcommand() {
2451 2451
                     ;;
2452 2452
             esac
2453 2453
             ;;
2454
+        (stack)
2455
+            local curcontext="$curcontext" state
2456
+            _arguments $(__docker_arguments) \
2457
+                $opts_help \
2458
+                "($help -): :->command" \
2459
+                "($help -)*:: :->option-or-argument" && ret=0
2460
+
2461
+            case $state in
2462
+                (command)
2463
+                    __docker_stack_commands && ret=0
2464
+                    ;;
2465
+                (option-or-argument)
2466
+                    curcontext=${curcontext%:*:*}:docker-${words[-1]}:
2467
+                    __docker_stack_subcommand && ret=0
2468
+                    ;;
2469
+            esac
2470
+            ;;
2454 2471
         (swarm)
2455 2472
             local curcontext="$curcontext" state
2456 2473
             _arguments $(__docker_arguments) \
... ...
@@ -16,6 +16,7 @@ import (
16 16
 	"time"
17 17
 
18 18
 	"github.com/Sirupsen/logrus"
19
+	"github.com/docker/distribution/digest"
19 20
 	distreference "github.com/docker/distribution/reference"
20 21
 	apierrors "github.com/docker/docker/api/errors"
21 22
 	apitypes "github.com/docker/docker/api/types"
... ...
@@ -1024,6 +1025,9 @@ func (c *Cluster) GetServices(options apitypes.ServiceListOptions) ([]types.Serv
1024 1024
 // TODO(nishanttotla): After the packages converge, the function must
1025 1025
 // convert distreference.Named -> distreference.Canonical, and the logic simplified.
1026 1026
 func (c *Cluster) imageWithDigestString(ctx context.Context, image string, authConfig *apitypes.AuthConfig) (string, error) {
1027
+	if _, err := digest.ParseDigest(image); err == nil {
1028
+		return "", errors.New("image reference is an image ID")
1029
+	}
1027 1030
 	ref, err := distreference.ParseNamed(image)
1028 1031
 	if err != nil {
1029 1032
 		return "", err
... ...
@@ -64,6 +64,13 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) types.ContainerSpec {
64 64
 				}
65 65
 			}
66 66
 		}
67
+
68
+		if m.TmpfsOptions != nil {
69
+			mount.TmpfsOptions = &mounttypes.TmpfsOptions{
70
+				SizeBytes: m.TmpfsOptions.SizeBytes,
71
+				Mode:      m.TmpfsOptions.Mode,
72
+			}
73
+		}
67 74
 		containerSpec.Mounts = append(containerSpec.Mounts, mount)
68 75
 	}
69 76
 
... ...
@@ -174,9 +181,7 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
174 174
 				mount.BindOptions = &swarmapi.Mount_BindOptions{Propagation: swarmapi.Mount_BindOptions_MountPropagation(mountPropagation)}
175 175
 			} else if string(m.BindOptions.Propagation) != "" {
176 176
 				return nil, fmt.Errorf("invalid MountPropagation: %q", m.BindOptions.Propagation)
177
-
178 177
 			}
179
-
180 178
 		}
181 179
 
182 180
 		if m.VolumeOptions != nil {
... ...
@@ -192,6 +197,13 @@ func containerToGRPC(c types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
192 192
 			}
193 193
 		}
194 194
 
195
+		if m.TmpfsOptions != nil {
196
+			mount.TmpfsOptions = &swarmapi.Mount_TmpfsOptions{
197
+				SizeBytes: m.TmpfsOptions.SizeBytes,
198
+				Mode:      m.TmpfsOptions.Mode,
199
+			}
200
+		}
201
+
195 202
 		containerSpec.Mounts = append(containerSpec.Mounts, mount)
196 203
 	}
197 204
 
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"time"
11 11
 
12 12
 	"github.com/Sirupsen/logrus"
13
+	"github.com/docker/distribution/digest"
13 14
 	"github.com/docker/docker/api/server/httputils"
14 15
 	"github.com/docker/docker/api/types"
15 16
 	"github.com/docker/docker/api/types/backend"
... ...
@@ -53,6 +54,11 @@ func newContainerAdapter(b executorpkg.Backend, task *api.Task, secrets exec.Sec
53 53
 func (c *containerAdapter) pullImage(ctx context.Context) error {
54 54
 	spec := c.container.spec()
55 55
 
56
+	// Skip pulling if the image is referenced by image ID.
57
+	if _, err := digest.ParseDigest(spec.Image); err == nil {
58
+		return nil
59
+	}
60
+
56 61
 	// Skip pulling if the image is referenced by digest and already
57 62
 	// exists locally.
58 63
 	named, err := reference.ParseNamed(spec.Image)
... ...
@@ -46,6 +46,11 @@ func merge(userConf, imageConf *containertypes.Config) error {
46 46
 			imageEnvKey := strings.Split(imageEnv, "=")[0]
47 47
 			for _, userEnv := range userConf.Env {
48 48
 				userEnvKey := strings.Split(userEnv, "=")[0]
49
+				if runtime.GOOS == "windows" {
50
+					// Case insensitive environment variables on Windows
51
+					imageEnvKey = strings.ToUpper(imageEnvKey)
52
+					userEnvKey = strings.ToUpper(userEnvKey)
53
+				}
49 54
 				if imageEnvKey == userEnvKey {
50 55
 					found = true
51 56
 					break
... ...
@@ -268,11 +268,11 @@ func (daemon *Daemon) verifyContainerSettings(hostConfig *containertypes.HostCon
268 268
 	switch p.Name {
269 269
 	case "always", "unless-stopped", "no":
270 270
 		if p.MaximumRetryCount != 0 {
271
-			return nil, fmt.Errorf("maximum restart count not valid with restart policy of '%s'", p.Name)
271
+			return nil, fmt.Errorf("maximum retry count cannot be used with restart policy '%s'", p.Name)
272 272
 		}
273 273
 	case "on-failure":
274
-		if p.MaximumRetryCount < 1 {
275
-			return nil, fmt.Errorf("maximum restart count must be a positive integer")
274
+		if p.MaximumRetryCount < 0 {
275
+			return nil, fmt.Errorf("maximum retry count cannot be negative")
276 276
 		}
277 277
 	case "":
278 278
 	// do nothing
... ...
@@ -40,7 +40,6 @@ import (
40 40
 	"github.com/docker/docker/libcontainerd"
41 41
 	"github.com/docker/docker/migrate/v1"
42 42
 	"github.com/docker/docker/pkg/fileutils"
43
-	"github.com/docker/docker/pkg/graphdb"
44 43
 	"github.com/docker/docker/pkg/idtools"
45 44
 	"github.com/docker/docker/pkg/plugingetter"
46 45
 	"github.com/docker/docker/pkg/progress"
... ...
@@ -158,7 +157,6 @@ func (daemon *Daemon) restore() error {
158 158
 		}
159 159
 	}
160 160
 
161
-	var migrateLegacyLinks bool
162 161
 	removeContainers := make(map[string]*container.Container)
163 162
 	restartContainers := make(map[*container.Container]chan struct{})
164 163
 	activeSandboxes := make(map[string]interface{})
... ...
@@ -190,6 +188,8 @@ func (daemon *Daemon) restore() error {
190 190
 			}
191 191
 		}
192 192
 	}
193
+
194
+	var migrateLegacyLinks bool // Not relevant on Windows
193 195
 	var wg sync.WaitGroup
194 196
 	var mapLock sync.Mutex
195 197
 	for _, c := range containers {
... ...
@@ -265,24 +265,15 @@ func (daemon *Daemon) restore() error {
265 265
 		return fmt.Errorf("Error initializing network controller: %v", err)
266 266
 	}
267 267
 
268
-	// migrate any legacy links from sqlite
269
-	linkdbFile := filepath.Join(daemon.root, "linkgraph.db")
270
-	var legacyLinkDB *graphdb.Database
268
+	// Perform migration of legacy sqlite links (no-op on Windows)
271 269
 	if migrateLegacyLinks {
272
-		legacyLinkDB, err = graphdb.NewSqliteConn(linkdbFile)
273
-		if err != nil {
274
-			return fmt.Errorf("error connecting to legacy link graph DB %s, container links may be lost: %v", linkdbFile, err)
270
+		if err := daemon.sqliteMigration(containers); err != nil {
271
+			return err
275 272
 		}
276
-		defer legacyLinkDB.Close()
277 273
 	}
278 274
 
279 275
 	// Now that all the containers are registered, register the links
280 276
 	for _, c := range containers {
281
-		if migrateLegacyLinks {
282
-			if err := daemon.migrateLegacySqliteLinks(legacyLinkDB, c); err != nil {
283
-				return err
284
-			}
285
-		}
286 277
 		if err := daemon.registerLinks(c, c.HostConfig); err != nil {
287 278
 			logrus.Errorf("failed to register link for container %s: %v", c.ID, err)
288 279
 		}
... ...
@@ -692,7 +683,11 @@ func NewDaemon(config *Config, registryService registry.Service, containerdRemot
692 692
 
693 693
 	// set up SIGUSR1 handler on Unix-like systems, or a Win32 global event
694 694
 	// on Windows to dump Go routine stacks
695
-	d.setupDumpStackTrap(config.Root)
695
+	stackDumpDir := config.Root
696
+	if execRoot := config.GetExecRoot(); execRoot != "" {
697
+		stackDumpDir = execRoot
698
+	}
699
+	d.setupDumpStackTrap(stackDumpDir)
696 700
 
697 701
 	return d, nil
698 702
 }
... ...
@@ -792,7 +787,13 @@ func (daemon *Daemon) Shutdown() error {
792 792
 		})
793 793
 	}
794 794
 
795
-	// Shutdown plugins after containers. Dont change the order.
795
+	if daemon.layerStore != nil {
796
+		if err := daemon.layerStore.Cleanup(); err != nil {
797
+			logrus.Errorf("Error during layer Store.Cleanup(): %v", err)
798
+		}
799
+	}
800
+
801
+	// Shutdown plugins after containers and layerstore. Don't change the order.
796 802
 	daemon.pluginShutdown()
797 803
 
798 804
 	// trigger libnetwork Stop only if it's initialized
... ...
@@ -800,12 +801,6 @@ func (daemon *Daemon) Shutdown() error {
800 800
 		daemon.netController.Stop()
801 801
 	}
802 802
 
803
-	if daemon.layerStore != nil {
804
-		if err := daemon.layerStore.Cleanup(); err != nil {
805
-			logrus.Errorf("Error during layer Store.Cleanup(): %v", err)
806
-		}
807
-	}
808
-
809 803
 	if err := daemon.cleanupMounts(); err != nil {
810 804
 		return err
811 805
 	}
... ...
@@ -1,12 +1,9 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
-	"strings"
5 4
 	"sync"
6 5
 
7
-	"github.com/Sirupsen/logrus"
8 6
 	"github.com/docker/docker/container"
9
-	"github.com/docker/docker/pkg/graphdb"
10 7
 )
11 8
 
12 9
 // linkIndex stores link relationships between containers, including their specified alias
... ...
@@ -88,41 +85,3 @@ func (l *linkIndex) delete(container *container.Container) {
88 88
 	delete(l.childIdx, container)
89 89
 	l.mu.Unlock()
90 90
 }
91
-
92
-// migrateLegacySqliteLinks migrates sqlite links to use links from HostConfig
93
-// when sqlite links were used, hostConfig.Links was set to nil
94
-func (daemon *Daemon) migrateLegacySqliteLinks(db *graphdb.Database, container *container.Container) error {
95
-	// if links is populated (or an empty slice), then this isn't using sqlite links and can be skipped
96
-	if container.HostConfig == nil || container.HostConfig.Links != nil {
97
-		return nil
98
-	}
99
-
100
-	logrus.Debugf("migrating legacy sqlite link info for container: %s", container.ID)
101
-
102
-	fullName := container.Name
103
-	if fullName[0] != '/' {
104
-		fullName = "/" + fullName
105
-	}
106
-
107
-	// don't use a nil slice, this ensures that the check above will skip once the migration has completed
108
-	links := []string{}
109
-	children, err := db.Children(fullName, 0)
110
-	if err != nil {
111
-		if !strings.Contains(err.Error(), "Cannot find child for") {
112
-			return err
113
-		}
114
-		// else continue... it's ok if we didn't find any children, it'll just be nil and we can continue the migration
115
-	}
116
-
117
-	for _, child := range children {
118
-		c, err := daemon.GetContainer(child.Entity.ID())
119
-		if err != nil {
120
-			return err
121
-		}
122
-
123
-		links = append(links, c.Name+":"+child.Edge.Name)
124
-	}
125
-
126
-	container.HostConfig.Links = links
127
-	return container.WriteHostConfig()
128
-}
129 91
new file mode 100644
... ...
@@ -0,0 +1,72 @@
0
+package daemon
1
+
2
+import (
3
+	"fmt"
4
+	"path/filepath"
5
+	"strings"
6
+
7
+	"github.com/Sirupsen/logrus"
8
+	"github.com/docker/docker/container"
9
+	"github.com/docker/docker/pkg/graphdb"
10
+)
11
+
12
+// migrateLegacySqliteLinks migrates sqlite links to use links from HostConfig
13
+// when sqlite links were used, hostConfig.Links was set to nil
14
+func (daemon *Daemon) migrateLegacySqliteLinks(db *graphdb.Database, container *container.Container) error {
15
+	// if links is populated (or an empty slice), then this isn't using sqlite links and can be skipped
16
+	if container.HostConfig == nil || container.HostConfig.Links != nil {
17
+		return nil
18
+	}
19
+
20
+	logrus.Debugf("migrating legacy sqlite link info for container: %s", container.ID)
21
+
22
+	fullName := container.Name
23
+	if fullName[0] != '/' {
24
+		fullName = "/" + fullName
25
+	}
26
+
27
+	// don't use a nil slice, this ensures that the check above will skip once the migration has completed
28
+	links := []string{}
29
+	children, err := db.Children(fullName, 0)
30
+	if err != nil {
31
+		if !strings.Contains(err.Error(), "Cannot find child for") {
32
+			return err
33
+		}
34
+		// else continue... it's ok if we didn't find any children, it'll just be nil and we can continue the migration
35
+	}
36
+
37
+	for _, child := range children {
38
+		c, err := daemon.GetContainer(child.Entity.ID())
39
+		if err != nil {
40
+			return err
41
+		}
42
+
43
+		links = append(links, c.Name+":"+child.Edge.Name)
44
+	}
45
+
46
+	container.HostConfig.Links = links
47
+	return container.WriteHostConfig()
48
+}
49
+
50
+// sqliteMigration performs the link graph DB migration.
51
+func (daemon *Daemon) sqliteMigration(containers map[string]*container.Container) error {
52
+	// migrate any legacy links from sqlite
53
+	linkdbFile := filepath.Join(daemon.root, "linkgraph.db")
54
+	var (
55
+		legacyLinkDB *graphdb.Database
56
+		err          error
57
+	)
58
+
59
+	legacyLinkDB, err = graphdb.NewSqliteConn(linkdbFile)
60
+	if err != nil {
61
+		return fmt.Errorf("error connecting to legacy link graph DB %s, container links may be lost: %v", linkdbFile, err)
62
+	}
63
+	defer legacyLinkDB.Close()
64
+
65
+	for _, c := range containers {
66
+		if err := daemon.migrateLegacySqliteLinks(legacyLinkDB, c); err != nil {
67
+			return err
68
+		}
69
+	}
70
+	return nil
71
+}
0 72
new file mode 100644
... ...
@@ -0,0 +1,98 @@
0
+package daemon
1
+
2
+import (
3
+	"encoding/json"
4
+	"io/ioutil"
5
+	"os"
6
+	"path"
7
+	"path/filepath"
8
+	"testing"
9
+
10
+	containertypes "github.com/docker/docker/api/types/container"
11
+	"github.com/docker/docker/container"
12
+	"github.com/docker/docker/pkg/graphdb"
13
+	"github.com/docker/docker/pkg/stringid"
14
+)
15
+
16
+func TestMigrateLegacySqliteLinks(t *testing.T) {
17
+	tmpDir, err := ioutil.TempDir("", "legacy-qlite-links-test")
18
+	if err != nil {
19
+		t.Fatal(err)
20
+	}
21
+	defer os.RemoveAll(tmpDir)
22
+
23
+	name1 := "test1"
24
+	c1 := &container.Container{
25
+		CommonContainer: container.CommonContainer{
26
+			ID:         stringid.GenerateNonCryptoID(),
27
+			Name:       name1,
28
+			HostConfig: &containertypes.HostConfig{},
29
+		},
30
+	}
31
+	c1.Root = tmpDir
32
+
33
+	name2 := "test2"
34
+	c2 := &container.Container{
35
+		CommonContainer: container.CommonContainer{
36
+			ID:   stringid.GenerateNonCryptoID(),
37
+			Name: name2,
38
+		},
39
+	}
40
+
41
+	store := container.NewMemoryStore()
42
+	store.Add(c1.ID, c1)
43
+	store.Add(c2.ID, c2)
44
+
45
+	d := &Daemon{root: tmpDir, containers: store}
46
+	db, err := graphdb.NewSqliteConn(filepath.Join(d.root, "linkgraph.db"))
47
+	if err != nil {
48
+		t.Fatal(err)
49
+	}
50
+
51
+	if _, err := db.Set("/"+name1, c1.ID); err != nil {
52
+		t.Fatal(err)
53
+	}
54
+
55
+	if _, err := db.Set("/"+name2, c2.ID); err != nil {
56
+		t.Fatal(err)
57
+	}
58
+
59
+	alias := "hello"
60
+	if _, err := db.Set(path.Join(c1.Name, alias), c2.ID); err != nil {
61
+		t.Fatal(err)
62
+	}
63
+
64
+	if err := d.migrateLegacySqliteLinks(db, c1); err != nil {
65
+		t.Fatal(err)
66
+	}
67
+
68
+	if len(c1.HostConfig.Links) != 1 {
69
+		t.Fatal("expected links to be populated but is empty")
70
+	}
71
+
72
+	expected := name2 + ":" + alias
73
+	actual := c1.HostConfig.Links[0]
74
+	if actual != expected {
75
+		t.Fatalf("got wrong link value, expected: %q, got: %q", expected, actual)
76
+	}
77
+
78
+	// ensure this is persisted
79
+	b, err := ioutil.ReadFile(filepath.Join(c1.Root, "hostconfig.json"))
80
+	if err != nil {
81
+		t.Fatal(err)
82
+	}
83
+	type hc struct {
84
+		Links []string
85
+	}
86
+	var cfg hc
87
+	if err := json.Unmarshal(b, &cfg); err != nil {
88
+		t.Fatal(err)
89
+	}
90
+
91
+	if len(cfg.Links) != 1 {
92
+		t.Fatalf("expected one entry in links, got: %d", len(cfg.Links))
93
+	}
94
+	if cfg.Links[0] != expected { // same expected as above
95
+		t.Fatalf("got wrong link value, expected: %q, got: %q", expected, cfg.Links[0])
96
+	}
97
+}
0 98
new file mode 100644
... ...
@@ -0,0 +1,10 @@
0
+// +build !linux
1
+
2
+package daemon
3
+
4
+import "github.com/docker/docker/container"
5
+
6
+// sqliteMigration performs the link graph DB migration. No-op on platforms other than Linux
7
+func (daemon *Daemon) sqliteMigration(_ map[string]*container.Container) error {
8
+	return nil
9
+}
0 10
deleted file mode 100644
... ...
@@ -1,98 +0,0 @@
1
-package daemon
2
-
3
-import (
4
-	"encoding/json"
5
-	"io/ioutil"
6
-	"os"
7
-	"path"
8
-	"path/filepath"
9
-	"testing"
10
-
11
-	containertypes "github.com/docker/docker/api/types/container"
12
-	"github.com/docker/docker/container"
13
-	"github.com/docker/docker/pkg/graphdb"
14
-	"github.com/docker/docker/pkg/stringid"
15
-)
16
-
17
-func TestMigrateLegacySqliteLinks(t *testing.T) {
18
-	tmpDir, err := ioutil.TempDir("", "legacy-qlite-links-test")
19
-	if err != nil {
20
-		t.Fatal(err)
21
-	}
22
-	defer os.RemoveAll(tmpDir)
23
-
24
-	name1 := "test1"
25
-	c1 := &container.Container{
26
-		CommonContainer: container.CommonContainer{
27
-			ID:         stringid.GenerateNonCryptoID(),
28
-			Name:       name1,
29
-			HostConfig: &containertypes.HostConfig{},
30
-		},
31
-	}
32
-	c1.Root = tmpDir
33
-
34
-	name2 := "test2"
35
-	c2 := &container.Container{
36
-		CommonContainer: container.CommonContainer{
37
-			ID:   stringid.GenerateNonCryptoID(),
38
-			Name: name2,
39
-		},
40
-	}
41
-
42
-	store := container.NewMemoryStore()
43
-	store.Add(c1.ID, c1)
44
-	store.Add(c2.ID, c2)
45
-
46
-	d := &Daemon{root: tmpDir, containers: store}
47
-	db, err := graphdb.NewSqliteConn(filepath.Join(d.root, "linkgraph.db"))
48
-	if err != nil {
49
-		t.Fatal(err)
50
-	}
51
-
52
-	if _, err := db.Set("/"+name1, c1.ID); err != nil {
53
-		t.Fatal(err)
54
-	}
55
-
56
-	if _, err := db.Set("/"+name2, c2.ID); err != nil {
57
-		t.Fatal(err)
58
-	}
59
-
60
-	alias := "hello"
61
-	if _, err := db.Set(path.Join(c1.Name, alias), c2.ID); err != nil {
62
-		t.Fatal(err)
63
-	}
64
-
65
-	if err := d.migrateLegacySqliteLinks(db, c1); err != nil {
66
-		t.Fatal(err)
67
-	}
68
-
69
-	if len(c1.HostConfig.Links) != 1 {
70
-		t.Fatal("expected links to be populated but is empty")
71
-	}
72
-
73
-	expected := name2 + ":" + alias
74
-	actual := c1.HostConfig.Links[0]
75
-	if actual != expected {
76
-		t.Fatalf("got wrong link value, expected: %q, got: %q", expected, actual)
77
-	}
78
-
79
-	// ensure this is persisted
80
-	b, err := ioutil.ReadFile(filepath.Join(c1.Root, "hostconfig.json"))
81
-	if err != nil {
82
-		t.Fatal(err)
83
-	}
84
-	type hc struct {
85
-		Links []string
86
-	}
87
-	var cfg hc
88
-	if err := json.Unmarshal(b, &cfg); err != nil {
89
-		t.Fatal(err)
90
-	}
91
-
92
-	if len(cfg.Links) != 1 {
93
-		t.Fatalf("expected one entry in links, got: %d", len(cfg.Links))
94
-	}
95
-	if cfg.Links[0] != expected { // same expected as above
96
-		t.Fatalf("got wrong link value, expected: %q, got: %q", expected, cfg.Links[0])
97
-	}
98
-}
... ...
@@ -49,7 +49,7 @@ func CopyMessage(msg *Message) *Message {
49 49
 	m.Timestamp = msg.Timestamp
50 50
 	m.Partial = msg.Partial
51 51
 	m.Attrs = make(LogAttributes)
52
-	for k, v := range m.Attrs {
52
+	for k, v := range msg.Attrs {
53 53
 		m.Attrs[k] = v
54 54
 	}
55 55
 	return m
56 56
new file mode 100644
... ...
@@ -0,0 +1,26 @@
0
+package logger
1
+
2
+import (
3
+	"reflect"
4
+	"testing"
5
+	"time"
6
+)
7
+
8
+func TestCopyMessage(t *testing.T) {
9
+	msg := &Message{
10
+		Line:      []byte("test line."),
11
+		Source:    "stdout",
12
+		Timestamp: time.Now(),
13
+		Attrs: LogAttributes{
14
+			"key1": "val1",
15
+			"key2": "val2",
16
+			"key3": "val3",
17
+		},
18
+		Partial: true,
19
+	}
20
+
21
+	m := CopyMessage(msg)
22
+	if !reflect.DeepEqual(m, msg) {
23
+		t.Fatalf("CopyMessage failed to copy message")
24
+	}
25
+}
... ...
@@ -6,6 +6,7 @@ import (
6 6
 	"syscall"
7 7
 
8 8
 	"github.com/Sirupsen/logrus"
9
+	"github.com/docker/distribution"
9 10
 	"github.com/docker/distribution/registry/api/errcode"
10 11
 	"github.com/docker/distribution/registry/api/v2"
11 12
 	"github.com/docker/distribution/registry/client"
... ...
@@ -139,6 +140,9 @@ func retryOnError(err error) error {
139 139
 	case *client.UnexpectedHTTPResponseError:
140 140
 		return xfer.DoNotRetry{Err: err}
141 141
 	case error:
142
+		if err == distribution.ErrBlobUnknown {
143
+			return xfer.DoNotRetry{Err: err}
144
+		}
142 145
 		if strings.Contains(err.Error(), strings.ToLower(syscall.ENOSPC.Error())) {
143 146
 			return xfer.DoNotRetry{Err: err}
144 147
 		}
... ...
@@ -189,9 +189,6 @@ func (ld *v2LayerDescriptor) Download(ctx context.Context, progressOutput progre
189 189
 	layerDownload, err := ld.open(ctx)
190 190
 	if err != nil {
191 191
 		logrus.Errorf("Error initiating layer download: %v", err)
192
-		if err == distribution.ErrBlobUnknown {
193
-			return nil, 0, xfer.DoNotRetry{Err: err}
194
-		}
195 192
 		return nil, 0, retryOnError(err)
196 193
 	}
197 194
 
... ...
@@ -523,6 +523,7 @@ func (pd *v2PushDescriptor) layerAlreadyExists(
523 523
 		layerDigests = append(layerDigests, meta.Digest)
524 524
 	}
525 525
 
526
+attempts:
526 527
 	for _, dgst := range layerDigests {
527 528
 		meta := digestToMetadata[dgst]
528 529
 		logrus.Debugf("Checking for presence of layer %s (%s) in %s", diffID, dgst, pd.repoInfo.FullName())
... ...
@@ -541,15 +542,14 @@ func (pd *v2PushDescriptor) layerAlreadyExists(
541 541
 			}
542 542
 			desc.MediaType = schema2.MediaTypeLayer
543 543
 			exists = true
544
-			break
544
+			break attempts
545 545
 		case distribution.ErrBlobUnknown:
546 546
 			if meta.SourceRepository == pd.repoInfo.FullName() {
547 547
 				// remove the mapping to the target repository
548 548
 				pd.v2MetadataService.Remove(*meta)
549 549
 			}
550 550
 		default:
551
-			progress.Update(progressOutput, pd.ID(), "Image push failed")
552
-			return desc, false, retryOnError(err)
551
+			logrus.WithError(err).Debugf("Failed to check for presence of layer %s (%s) in %s", diffID, dgst, pd.repoInfo.FullName())
553 552
 		}
554 553
 	}
555 554
 
... ...
@@ -180,7 +180,7 @@ func TestLayerAlreadyExists(t *testing.T) {
180 180
 			maxExistenceChecks: 1,
181 181
 			metadata:           []metadata.V2Metadata{{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/busybox"}},
182 182
 			remoteErrors:       map[digest.Digest]error{digest.Digest("apple"): distribution.ErrAccessDenied},
183
-			expectedError:      distribution.ErrAccessDenied,
183
+			expectedError:      nil,
184 184
 			expectedRequests:   []string{"apple"},
185 185
 		},
186 186
 		{
... ...
@@ -310,7 +310,7 @@ func TestLayerAlreadyExists(t *testing.T) {
310 310
 			expectedRemovals:   []metadata.V2Metadata{taggedMetadata("key3", "apple", "docker.io/library/busybox")},
311 311
 		},
312 312
 		{
313
-			name:       "stop on first error",
313
+			name:       "don't stop on first error",
314 314
 			targetRepo: "user/app",
315 315
 			hmacKey:    "key",
316 316
 			metadata: []metadata.V2Metadata{
... ...
@@ -321,9 +321,12 @@ func TestLayerAlreadyExists(t *testing.T) {
321 321
 			maxExistenceChecks: 3,
322 322
 			remoteErrors:       map[digest.Digest]error{"orange": distribution.ErrAccessDenied},
323 323
 			remoteBlobs:        map[digest.Digest]distribution.Descriptor{digest.Digest("apple"): {}},
324
-			expectedError:      distribution.ErrAccessDenied,
325
-			expectedRequests:   []string{"plum", "orange"},
326
-			expectedRemovals:   []metadata.V2Metadata{taggedMetadata("key", "plum", "docker.io/user/app")},
324
+			expectedError:      nil,
325
+			expectedRequests:   []string{"plum", "orange", "banana"},
326
+			expectedRemovals: []metadata.V2Metadata{
327
+				taggedMetadata("key", "plum", "docker.io/user/app"),
328
+				taggedMetadata("key", "banana", "docker.io/user/app"),
329
+			},
327 330
 		},
328 331
 		{
329 332
 			name:       "remove outdated metadata",
... ...
@@ -16,9 +16,7 @@ redirect_from:
16 16
      will be rejected.
17 17
 -->
18 18
 
19
-# Docker Engine API v1.18
20
-
21
-# 1. Brief introduction
19
+## 1. Brief introduction
22 20
 
23 21
  - The daemon listens on `unix:///var/run/docker.sock` but you can
24 22
    [Bind Docker to another host/port or a Unix socket](../commandline/dockerd.md#bind-docker-to-another-host-port-or-a-unix-socket).
... ...
@@ -26,11 +24,11 @@ redirect_from:
26 26
    or `pull`, the HTTP connection is hijacked to transport `STDOUT`,
27 27
    `STDIN` and `STDERR`.
28 28
 
29
-# 2. Endpoints
29
+## 2. Endpoints
30 30
 
31
-## 2.1 Containers
31
+### 2.1 Containers
32 32
 
33
-### List containers
33
+#### List containers
34 34
 
35 35
 `GET /containers/json`
36 36
 
... ...
@@ -123,7 +121,7 @@ List containers
123 123
 -   **400** – bad parameter
124 124
 -   **500** – server error
125 125
 
126
-### Create a container
126
+#### Create a container
127 127
 
128 128
 `POST /containers/create`
129 129
 
... ...
@@ -310,7 +308,7 @@ Create a container
310 310
 -   **409** – conflict
311 311
 -   **500** – server error
312 312
 
313
-### Inspect a container
313
+#### Inspect a container
314 314
 
315 315
 `GET /containers/(id or name)/json`
316 316
 
... ...
@@ -443,7 +441,7 @@ Return low-level information on the container `id`
443 443
 -   **404** – no such container
444 444
 -   **500** – server error
445 445
 
446
-### List processes running inside a container
446
+#### List processes running inside a container
447 447
 
448 448
 `GET /containers/(id or name)/top`
449 449
 
... ...
@@ -507,7 +505,7 @@ supported on Windows.
507 507
 -   **404** – no such container
508 508
 -   **500** – server error
509 509
 
510
-### Get container logs
510
+#### Get container logs
511 511
 
512 512
 `GET /containers/(id or name)/logs`
513 513
 
... ...
@@ -547,7 +545,7 @@ Get `stdout` and `stderr` logs from the container ``id``
547 547
 -   **404** – no such container
548 548
 -   **500** – server error
549 549
 
550
-### Inspect changes on a container's filesystem
550
+#### Inspect changes on a container's filesystem
551 551
 
552 552
 `GET /containers/(id or name)/changes`
553 553
 
... ...
@@ -589,7 +587,7 @@ Values for `Kind`:
589 589
 -   **404** – no such container
590 590
 -   **500** – server error
591 591
 
592
-### Export a container
592
+#### Export a container
593 593
 
594 594
 `GET /containers/(id or name)/export`
595 595
 
... ...
@@ -614,7 +612,7 @@ Export the contents of container `id`
614 614
 -   **404** – no such container
615 615
 -   **500** – server error
616 616
 
617
-### Get container stats based on resource usage
617
+#### Get container stats based on resource usage
618 618
 
619 619
 `GET /containers/(id or name)/stats`
620 620
 
... ...
@@ -702,7 +700,7 @@ This endpoint returns a live stream of a container's resource usage statistics.
702 702
 -   **404** – no such container
703 703
 -   **500** – server error
704 704
 
705
-### Resize a container TTY
705
+#### Resize a container TTY
706 706
 
707 707
 `POST /containers/(id or name)/resize?h=<height>&w=<width>`
708 708
 
... ...
@@ -729,7 +727,7 @@ Resize the TTY for container with  `id`. You must restart the container for the
729 729
 -   **404** – No such container
730 730
 -   **500** – Cannot resize container
731 731
 
732
-### Start a container
732
+#### Start a container
733 733
 
734 734
 `POST /containers/(id or name)/start`
735 735
 
... ...
@@ -754,7 +752,7 @@ Start the container `id`
754 754
 -   **404** – no such container
755 755
 -   **500** – server error
756 756
 
757
-### Stop a container
757
+#### Stop a container
758 758
 
759 759
 `POST /containers/(id or name)/stop`
760 760
 
... ...
@@ -779,7 +777,7 @@ Stop the container `id`
779 779
 -   **404** – no such container
780 780
 -   **500** – server error
781 781
 
782
-### Restart a container
782
+#### Restart a container
783 783
 
784 784
 `POST /containers/(id or name)/restart`
785 785
 
... ...
@@ -803,7 +801,7 @@ Restart the container `id`
803 803
 -   **404** – no such container
804 804
 -   **500** – server error
805 805
 
806
-### Kill a container
806
+#### Kill a container
807 807
 
808 808
 `POST /containers/(id or name)/kill`
809 809
 
... ...
@@ -828,7 +826,7 @@ Kill the container `id`
828 828
 -   **404** – no such container
829 829
 -   **500** – server error
830 830
 
831
-### Rename a container
831
+#### Rename a container
832 832
 
833 833
 `POST /containers/(id or name)/rename`
834 834
 
... ...
@@ -853,7 +851,7 @@ Rename the container `id` to a `new_name`
853 853
 -   **409** - conflict name already assigned
854 854
 -   **500** – server error
855 855
 
856
-### Pause a container
856
+#### Pause a container
857 857
 
858 858
 `POST /containers/(id or name)/pause`
859 859
 
... ...
@@ -873,7 +871,7 @@ Pause the container `id`
873 873
 -   **404** – no such container
874 874
 -   **500** – server error
875 875
 
876
-### Unpause a container
876
+#### Unpause a container
877 877
 
878 878
 `POST /containers/(id or name)/unpause`
879 879
 
... ...
@@ -893,7 +891,7 @@ Unpause the container `id`
893 893
 -   **404** – no such container
894 894
 -   **500** – server error
895 895
 
896
-### Attach to a container
896
+#### Attach to a container
897 897
 
898 898
 `POST /containers/(id or name)/attach`
899 899
 
... ...
@@ -978,7 +976,7 @@ The simplest way to implement the Attach protocol is the following:
978 978
     4.  Read the extracted size and output it on the correct output.
979 979
     5.  Goto 1.
980 980
 
981
-### Attach to a container (websocket)
981
+#### Attach to a container (websocket)
982 982
 
983 983
 `GET /containers/(id or name)/attach/ws`
984 984
 
... ...
@@ -1015,7 +1013,7 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet
1015 1015
 -   **404** – no such container
1016 1016
 -   **500** – server error
1017 1017
 
1018
-### Wait a container
1018
+#### Wait a container
1019 1019
 
1020 1020
 `POST /containers/(id or name)/wait`
1021 1021
 
... ...
@@ -1038,7 +1036,7 @@ Block until container `id` stops, then returns the exit code
1038 1038
 -   **404** – no such container
1039 1039
 -   **500** – server error
1040 1040
 
1041
-### Remove a container
1041
+#### Remove a container
1042 1042
 
1043 1043
 `DELETE /containers/(id or name)`
1044 1044
 
... ...
@@ -1067,7 +1065,7 @@ Remove the container `id` from the filesystem
1067 1067
 -   **409** – conflict
1068 1068
 -   **500** – server error
1069 1069
 
1070
-### Copy files or folders from a container
1070
+#### Copy files or folders from a container
1071 1071
 
1072 1072
 `POST /containers/(id or name)/copy`
1073 1073
 
... ...
@@ -1097,9 +1095,9 @@ Copy files or folders of container `id`
1097 1097
 -   **404** – no such container
1098 1098
 -   **500** – server error
1099 1099
 
1100
-## 2.2 Images
1100
+### 2.2 Images
1101 1101
 
1102
-### List Images
1102
+#### List Images
1103 1103
 
1104 1104
 `GET /images/json`
1105 1105
 
... ...
@@ -1185,7 +1183,7 @@ references on the command line.
1185 1185
   -   `label=key` or `label="key=value"` of an image label
1186 1186
 -   **filter** - only return images with the specified name
1187 1187
 
1188
-### Build image from a Dockerfile
1188
+#### Build image from a Dockerfile
1189 1189
 
1190 1190
 `POST /build`
1191 1191
 
... ...
@@ -1256,7 +1254,7 @@ or being killed.
1256 1256
 -   **200** – no error
1257 1257
 -   **500** – server error
1258 1258
 
1259
-### Create an image
1259
+#### Create an image
1260 1260
 
1261 1261
 `POST /images/create`
1262 1262
 
... ...
@@ -1300,7 +1298,7 @@ a base64-encoded AuthConfig object.
1300 1300
 
1301 1301
 
1302 1302
 
1303
-### Inspect an image
1303
+#### Inspect an image
1304 1304
 
1305 1305
 `GET /images/(name)/json`
1306 1306
 
... ...
@@ -1351,7 +1349,7 @@ Return low-level information on the image `name`
1351 1351
 -   **404** – no such image
1352 1352
 -   **500** – server error
1353 1353
 
1354
-### Get the history of an image
1354
+#### Get the history of an image
1355 1355
 
1356 1356
 `GET /images/(name)/history`
1357 1357
 
... ...
@@ -1385,7 +1383,7 @@ Return the history of the image `name`
1385 1385
 -   **404** – no such image
1386 1386
 -   **500** – server error
1387 1387
 
1388
-### Push an image on the registry
1388
+#### Push an image on the registry
1389 1389
 
1390 1390
 `POST /images/(name)/push`
1391 1391
 
... ...
@@ -1428,7 +1426,7 @@ then be used in the URL. This duplicates the command line's flow.
1428 1428
 -   **404** – no such image
1429 1429
 -   **500** – server error
1430 1430
 
1431
-### Tag an image into a repository
1431
+#### Tag an image into a repository
1432 1432
 
1433 1433
 `POST /images/(name)/tag`
1434 1434
 
... ...
@@ -1456,7 +1454,7 @@ Tag the image `name` into a repository
1456 1456
 -   **409** – conflict
1457 1457
 -   **500** – server error
1458 1458
 
1459
-### Remove an image
1459
+#### Remove an image
1460 1460
 
1461 1461
 `DELETE /images/(name)`
1462 1462
 
... ...
@@ -1489,7 +1487,7 @@ Remove the image `name` from the filesystem
1489 1489
 -   **409** – conflict
1490 1490
 -   **500** – server error
1491 1491
 
1492
-### Search images
1492
+#### Search images
1493 1493
 
1494 1494
 `GET /images/search`
1495 1495
 
... ...
@@ -1542,9 +1540,9 @@ Search for an image on [Docker Hub](https://hub.docker.com).
1542 1542
 -   **200** – no error
1543 1543
 -   **500** – server error
1544 1544
 
1545
-## 2.3 Misc
1545
+### 2.3 Misc
1546 1546
 
1547
-### Check auth configuration
1547
+#### Check auth configuration
1548 1548
 
1549 1549
 `POST /auth`
1550 1550
 
... ...
@@ -1572,7 +1570,7 @@ Get the default username and email
1572 1572
 -   **204** – no error
1573 1573
 -   **500** – server error
1574 1574
 
1575
-### Display system-wide information
1575
+#### Display system-wide information
1576 1576
 
1577 1577
 `GET /info`
1578 1578
 
... ...
@@ -1637,7 +1635,7 @@ Display system-wide information
1637 1637
 -   **200** – no error
1638 1638
 -   **500** – server error
1639 1639
 
1640
-### Show the docker version information
1640
+#### Show the docker version information
1641 1641
 
1642 1642
 `GET /version`
1643 1643
 
... ...
@@ -1667,7 +1665,7 @@ Show the docker version information
1667 1667
 -   **200** – no error
1668 1668
 -   **500** – server error
1669 1669
 
1670
-### Ping the docker server
1670
+#### Ping the docker server
1671 1671
 
1672 1672
 `GET /_ping`
1673 1673
 
... ...
@@ -1689,7 +1687,7 @@ Ping the docker server
1689 1689
 -   **200** - no error
1690 1690
 -   **500** - server error
1691 1691
 
1692
-### Create a new image from a container's changes
1692
+#### Create a new image from a container's changes
1693 1693
 
1694 1694
 `POST /commit`
1695 1695
 
... ...
@@ -1751,7 +1749,7 @@ Create a new image from a container's changes
1751 1751
 -   **404** – no such container
1752 1752
 -   **500** – server error
1753 1753
 
1754
-### Monitor Docker's events
1754
+#### Monitor Docker's events
1755 1755
 
1756 1756
 `GET /events`
1757 1757
 
... ...
@@ -1793,7 +1791,7 @@ Docker images report the following events:
1793 1793
 -   **200** – no error
1794 1794
 -   **500** – server error
1795 1795
 
1796
-### Get a tarball containing all images in a repository
1796
+#### Get a tarball containing all images in a repository
1797 1797
 
1798 1798
 `GET /images/(name)/get`
1799 1799
 
... ...
@@ -1823,7 +1821,7 @@ See the [image tarball format](#image-tarball-format) for more details.
1823 1823
 -   **200** – no error
1824 1824
 -   **500** – server error
1825 1825
 
1826
-### Get a tarball containing all images
1826
+#### Get a tarball containing all images
1827 1827
 
1828 1828
 `GET /images/get`
1829 1829
 
... ...
@@ -1852,7 +1850,7 @@ See the [image tarball format](#image-tarball-format) for more details.
1852 1852
 -   **200** – no error
1853 1853
 -   **500** – server error
1854 1854
 
1855
-### Load a tarball with a set of images and tags into docker
1855
+#### Load a tarball with a set of images and tags into docker
1856 1856
 
1857 1857
 `POST /images/load`
1858 1858
 
... ...
@@ -1875,7 +1873,7 @@ See the [image tarball format](#image-tarball-format) for more details.
1875 1875
 -   **200** – no error
1876 1876
 -   **500** – server error
1877 1877
 
1878
-### Image tarball format
1878
+#### Image tarball format
1879 1879
 
1880 1880
 An image tarball contains one directory per image layer (named using its long ID),
1881 1881
 each containing these files:
... ...
@@ -1896,7 +1894,7 @@ the root that contains a list of repository and tag names mapped to layer IDs.
1896 1896
 }
1897 1897
 ```
1898 1898
 
1899
-### Exec Create
1899
+#### Exec Create
1900 1900
 
1901 1901
 `POST /containers/(id or name)/exec`
1902 1902
 
... ...
@@ -1939,7 +1937,7 @@ Sets up an exec instance in a running container `id`
1939 1939
 -   **201** – no error
1940 1940
 -   **404** – no such container
1941 1941
 
1942
-### Exec Start
1942
+#### Exec Start
1943 1943
 
1944 1944
 `POST /exec/(id)/start`
1945 1945
 
... ...
@@ -1980,7 +1978,7 @@ interactive session with the `exec` command.
1980 1980
 
1981 1981
 Similar to the stream behavior of `POST /containers/(id or name)/attach` API
1982 1982
 
1983
-### Exec Resize
1983
+#### Exec Resize
1984 1984
 
1985 1985
 `POST /exec/(id)/resize`
1986 1986
 
... ...
@@ -2007,7 +2005,7 @@ This API is valid only if `tty` was specified as part of creating and starting t
2007 2007
 -   **201** – no error
2008 2008
 -   **404** – no such exec instance
2009 2009
 
2010
-### Exec Inspect
2010
+#### Exec Inspect
2011 2011
 
2012 2012
 `GET /exec/(id)/json`
2013 2013
 
... ...
@@ -2112,9 +2110,9 @@ Return low-level information about the `exec` command `id`.
2112 2112
 -   **404** – no such exec instance
2113 2113
 -   **500** - server error
2114 2114
 
2115
-# 3. Going further
2115
+## 3. Going further
2116 2116
 
2117
-## 3.1 Inside `docker run`
2117
+### 3.1 Inside `docker run`
2118 2118
 
2119 2119
 As an example, the `docker run` command line makes the following API calls:
2120 2120
 
... ...
@@ -2132,7 +2130,7 @@ As an example, the `docker run` command line makes the following API calls:
2132 2132
 
2133 2133
 - If in detached mode or only `stdin` is attached, display the container's id.
2134 2134
 
2135
-## 3.2 Hijacking
2135
+### 3.2 Hijacking
2136 2136
 
2137 2137
 In this version of the API, `/attach`, uses hijacking to transport `stdin`,
2138 2138
 `stdout`, and `stderr` on the same socket.
... ...
@@ -2148,7 +2146,7 @@ from **200 OK** to **101 UPGRADED** and resends the same headers.
2148 2148
 
2149 2149
 This might change in the future.
2150 2150
 
2151
-## 3.3 CORS Requests
2151
+### 3.3 CORS Requests
2152 2152
 
2153 2153
 To set cross origin requests to the Engine API please give values to
2154 2154
 `--api-cors-header` when running Docker in daemon mode. Set * (asterisk) allows all,
... ...
@@ -16,8 +16,6 @@ redirect_from:
16 16
      will be rejected.
17 17
 -->
18 18
 
19
-# Docker Engine API v1.19
20
-
21 19
 ## 1. Brief introduction
22 20
 
23 21
  - The daemon listens on `unix:///var/run/docker.sock` but you can
... ...
@@ -28,11 +26,11 @@ redirect_from:
28 28
  - When the client API version is newer than the daemon's, these calls return an HTTP
29 29
    `400 Bad Request` error message.
30 30
 
31
-# 2. Endpoints
31
+## 2. Endpoints
32 32
 
33
-## 2.1 Containers
33
+### 2.1 Containers
34 34
 
35
-### List containers
35
+#### List containers
36 36
 
37 37
 `GET /containers/json`
38 38
 
... ...
@@ -125,7 +123,7 @@ List containers
125 125
 -   **400** – bad parameter
126 126
 -   **500** – server error
127 127
 
128
-### Create a container
128
+#### Create a container
129 129
 
130 130
 `POST /containers/create`
131 131
 
... ...
@@ -322,7 +320,7 @@ Create a container
322 322
 -   **409** – conflict
323 323
 -   **500** – server error
324 324
 
325
-### Inspect a container
325
+#### Inspect a container
326 326
 
327 327
 `GET /containers/(id or name)/json`
328 328
 
... ...
@@ -459,7 +457,7 @@ Return low-level information on the container `id`
459 459
 -   **404** – no such container
460 460
 -   **500** – server error
461 461
 
462
-### List processes running inside a container
462
+#### List processes running inside a container
463 463
 
464 464
 `GET /containers/(id or name)/top`
465 465
 
... ...
@@ -523,7 +521,7 @@ supported on Windows.
523 523
 -   **404** – no such container
524 524
 -   **500** – server error
525 525
 
526
-### Get container logs
526
+#### Get container logs
527 527
 
528 528
 `GET /containers/(id or name)/logs`
529 529
 
... ...
@@ -565,7 +563,7 @@ Get `stdout` and `stderr` logs from the container ``id``
565 565
 -   **404** – no such container
566 566
 -   **500** – server error
567 567
 
568
-### Inspect changes on a container's filesystem
568
+#### Inspect changes on a container's filesystem
569 569
 
570 570
 `GET /containers/(id or name)/changes`
571 571
 
... ...
@@ -607,7 +605,7 @@ Values for `Kind`:
607 607
 -   **404** – no such container
608 608
 -   **500** – server error
609 609
 
610
-### Export a container
610
+#### Export a container
611 611
 
612 612
 `GET /containers/(id or name)/export`
613 613
 
... ...
@@ -632,7 +630,7 @@ Export the contents of container `id`
632 632
 -   **404** – no such container
633 633
 -   **500** – server error
634 634
 
635
-### Get container stats based on resource usage
635
+#### Get container stats based on resource usage
636 636
 
637 637
 `GET /containers/(id or name)/stats`
638 638
 
... ...
@@ -741,7 +739,7 @@ The precpu_stats is the cpu statistic of last read, which is used for calculatin
741 741
 -   **404** – no such container
742 742
 -   **500** – server error
743 743
 
744
-### Resize a container TTY
744
+#### Resize a container TTY
745 745
 
746 746
 `POST /containers/(id or name)/resize?h=<height>&w=<width>`
747 747
 
... ...
@@ -768,7 +766,7 @@ Resize the TTY for container with  `id`. You must restart the container for the
768 768
 -   **404** – No such container
769 769
 -   **500** – Cannot resize container
770 770
 
771
-### Start a container
771
+#### Start a container
772 772
 
773 773
 `POST /containers/(id or name)/start`
774 774
 
... ...
@@ -793,7 +791,7 @@ Start the container `id`
793 793
 -   **404** – no such container
794 794
 -   **500** – server error
795 795
 
796
-### Stop a container
796
+#### Stop a container
797 797
 
798 798
 `POST /containers/(id or name)/stop`
799 799
 
... ...
@@ -818,7 +816,7 @@ Stop the container `id`
818 818
 -   **404** – no such container
819 819
 -   **500** – server error
820 820
 
821
-### Restart a container
821
+#### Restart a container
822 822
 
823 823
 `POST /containers/(id or name)/restart`
824 824
 
... ...
@@ -842,7 +840,7 @@ Restart the container `id`
842 842
 -   **404** – no such container
843 843
 -   **500** – server error
844 844
 
845
-### Kill a container
845
+#### Kill a container
846 846
 
847 847
 `POST /containers/(id or name)/kill`
848 848
 
... ...
@@ -867,7 +865,7 @@ Kill the container `id`
867 867
 -   **404** – no such container
868 868
 -   **500** – server error
869 869
 
870
-### Rename a container
870
+#### Rename a container
871 871
 
872 872
 `POST /containers/(id or name)/rename`
873 873
 
... ...
@@ -892,7 +890,7 @@ Rename the container `id` to a `new_name`
892 892
 -   **409** - conflict name already assigned
893 893
 -   **500** – server error
894 894
 
895
-### Pause a container
895
+#### Pause a container
896 896
 
897 897
 `POST /containers/(id or name)/pause`
898 898
 
... ...
@@ -912,7 +910,7 @@ Pause the container `id`
912 912
 -   **404** – no such container
913 913
 -   **500** – server error
914 914
 
915
-### Unpause a container
915
+#### Unpause a container
916 916
 
917 917
 `POST /containers/(id or name)/unpause`
918 918
 
... ...
@@ -932,7 +930,7 @@ Unpause the container `id`
932 932
 -   **404** – no such container
933 933
 -   **500** – server error
934 934
 
935
-### Attach to a container
935
+#### Attach to a container
936 936
 
937 937
 `POST /containers/(id or name)/attach`
938 938
 
... ...
@@ -1017,7 +1015,7 @@ The simplest way to implement the Attach protocol is the following:
1017 1017
     4.  Read the extracted size and output it on the correct output.
1018 1018
     5.  Goto 1.
1019 1019
 
1020
-### Attach to a container (websocket)
1020
+#### Attach to a container (websocket)
1021 1021
 
1022 1022
 `GET /containers/(id or name)/attach/ws`
1023 1023
 
... ...
@@ -1054,7 +1052,7 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet
1054 1054
 -   **404** – no such container
1055 1055
 -   **500** – server error
1056 1056
 
1057
-### Wait a container
1057
+#### Wait a container
1058 1058
 
1059 1059
 `POST /containers/(id or name)/wait`
1060 1060
 
... ...
@@ -1077,7 +1075,7 @@ Block until container `id` stops, then returns the exit code
1077 1077
 -   **404** – no such container
1078 1078
 -   **500** – server error
1079 1079
 
1080
-### Remove a container
1080
+#### Remove a container
1081 1081
 
1082 1082
 `DELETE /containers/(id or name)`
1083 1083
 
... ...
@@ -1106,7 +1104,7 @@ Remove the container `id` from the filesystem
1106 1106
 -   **409** – conflict
1107 1107
 -   **500** – server error
1108 1108
 
1109
-### Copy files or folders from a container
1109
+#### Copy files or folders from a container
1110 1110
 
1111 1111
 `POST /containers/(id or name)/copy`
1112 1112
 
... ...
@@ -1136,9 +1134,9 @@ Copy files or folders of container `id`
1136 1136
 -   **404** – no such container
1137 1137
 -   **500** – server error
1138 1138
 
1139
-## 2.2 Images
1139
+### 2.2 Images
1140 1140
 
1141
-### List Images
1141
+#### List Images
1142 1142
 
1143 1143
 `GET /images/json`
1144 1144
 
... ...
@@ -1229,7 +1227,7 @@ references on the command line.
1229 1229
   -   `label=key` or `label="key=value"` of an image label
1230 1230
 -   **filter** - only return images with the specified name
1231 1231
 
1232
-### Build image from a Dockerfile
1232
+#### Build image from a Dockerfile
1233 1233
 
1234 1234
 `POST /build`
1235 1235
 
... ...
@@ -1302,7 +1300,7 @@ or being killed.
1302 1302
 -   **200** – no error
1303 1303
 -   **500** – server error
1304 1304
 
1305
-### Create an image
1305
+#### Create an image
1306 1306
 
1307 1307
 `POST /images/create`
1308 1308
 
... ...
@@ -1346,7 +1344,7 @@ a base64-encoded AuthConfig object.
1346 1346
 
1347 1347
 
1348 1348
 
1349
-### Inspect an image
1349
+#### Inspect an image
1350 1350
 
1351 1351
 `GET /images/(name)/json`
1352 1352
 
... ...
@@ -1397,7 +1395,7 @@ Return low-level information on the image `name`
1397 1397
 -   **404** – no such image
1398 1398
 -   **500** – server error
1399 1399
 
1400
-### Get the history of an image
1400
+#### Get the history of an image
1401 1401
 
1402 1402
 `GET /images/(name)/history`
1403 1403
 
... ...
@@ -1451,7 +1449,7 @@ Return the history of the image `name`
1451 1451
 -   **404** – no such image
1452 1452
 -   **500** – server error
1453 1453
 
1454
-### Push an image on the registry
1454
+#### Push an image on the registry
1455 1455
 
1456 1456
 `POST /images/(name)/push`
1457 1457
 
... ...
@@ -1494,7 +1492,7 @@ then be used in the URL. This duplicates the command line's flow.
1494 1494
 -   **404** – no such image
1495 1495
 -   **500** – server error
1496 1496
 
1497
-### Tag an image into a repository
1497
+#### Tag an image into a repository
1498 1498
 
1499 1499
 `POST /images/(name)/tag`
1500 1500
 
... ...
@@ -1522,7 +1520,7 @@ Tag the image `name` into a repository
1522 1522
 -   **409** – conflict
1523 1523
 -   **500** – server error
1524 1524
 
1525
-### Remove an image
1525
+#### Remove an image
1526 1526
 
1527 1527
 `DELETE /images/(name)`
1528 1528
 
... ...
@@ -1555,7 +1553,7 @@ Remove the image `name` from the filesystem
1555 1555
 -   **409** – conflict
1556 1556
 -   **500** – server error
1557 1557
 
1558
-### Search images
1558
+#### Search images
1559 1559
 
1560 1560
 `GET /images/search`
1561 1561
 
... ...
@@ -1614,9 +1612,9 @@ be deprecated and replaced by the `is_automated` property.
1614 1614
 -   **200** – no error
1615 1615
 -   **500** – server error
1616 1616
 
1617
-## 2.3 Misc
1617
+### 2.3 Misc
1618 1618
 
1619
-### Check auth configuration
1619
+#### Check auth configuration
1620 1620
 
1621 1621
 `POST /auth`
1622 1622
 
... ...
@@ -1644,7 +1642,7 @@ Get the default username and email
1644 1644
 -   **204** – no error
1645 1645
 -   **500** – server error
1646 1646
 
1647
-### Display system-wide information
1647
+#### Display system-wide information
1648 1648
 
1649 1649
 `GET /info`
1650 1650
 
... ...
@@ -1713,7 +1711,7 @@ Display system-wide information
1713 1713
 -   **200** – no error
1714 1714
 -   **500** – server error
1715 1715
 
1716
-### Show the docker version information
1716
+#### Show the docker version information
1717 1717
 
1718 1718
 `GET /version`
1719 1719
 
... ...
@@ -1743,7 +1741,7 @@ Show the docker version information
1743 1743
 -   **200** – no error
1744 1744
 -   **500** – server error
1745 1745
 
1746
-### Ping the docker server
1746
+#### Ping the docker server
1747 1747
 
1748 1748
 `GET /_ping`
1749 1749
 
... ...
@@ -1765,7 +1763,7 @@ Ping the docker server
1765 1765
 -   **200** - no error
1766 1766
 -   **500** - server error
1767 1767
 
1768
-### Create a new image from a container's changes
1768
+#### Create a new image from a container's changes
1769 1769
 
1770 1770
 `POST /commit`
1771 1771
 
... ...
@@ -1831,7 +1829,7 @@ Create a new image from a container's changes
1831 1831
 -   **404** – no such container
1832 1832
 -   **500** – server error
1833 1833
 
1834
-### Monitor Docker's events
1834
+#### Monitor Docker's events
1835 1835
 
1836 1836
 `GET /events`
1837 1837
 
... ...
@@ -1873,7 +1871,7 @@ Docker images report the following events:
1873 1873
 -   **200** – no error
1874 1874
 -   **500** – server error
1875 1875
 
1876
-### Get a tarball containing all images in a repository
1876
+#### Get a tarball containing all images in a repository
1877 1877
 
1878 1878
 `GET /images/(name)/get`
1879 1879
 
... ...
@@ -1903,7 +1901,7 @@ See the [image tarball format](#image-tarball-format) for more details.
1903 1903
 -   **200** – no error
1904 1904
 -   **500** – server error
1905 1905
 
1906
-### Get a tarball containing all images
1906
+#### Get a tarball containing all images
1907 1907
 
1908 1908
 `GET /images/get`
1909 1909
 
... ...
@@ -1932,7 +1930,7 @@ See the [image tarball format](#image-tarball-format) for more details.
1932 1932
 -   **200** – no error
1933 1933
 -   **500** – server error
1934 1934
 
1935
-### Load a tarball with a set of images and tags into docker
1935
+#### Load a tarball with a set of images and tags into docker
1936 1936
 
1937 1937
 `POST /images/load`
1938 1938
 
... ...
@@ -1955,7 +1953,7 @@ See the [image tarball format](#image-tarball-format) for more details.
1955 1955
 -   **200** – no error
1956 1956
 -   **500** – server error
1957 1957
 
1958
-### Image tarball format
1958
+#### Image tarball format
1959 1959
 
1960 1960
 An image tarball contains one directory per image layer (named using its long ID),
1961 1961
 each containing these files:
... ...
@@ -1976,7 +1974,7 @@ the root that contains a list of repository and tag names mapped to layer IDs.
1976 1976
 }
1977 1977
 ```
1978 1978
 
1979
-### Exec Create
1979
+#### Exec Create
1980 1980
 
1981 1981
 `POST /containers/(id or name)/exec`
1982 1982
 
... ...
@@ -2022,7 +2020,7 @@ Sets up an exec instance in a running container `id`
2022 2022
 -   **201** – no error
2023 2023
 -   **404** – no such container
2024 2024
 
2025
-### Exec Start
2025
+#### Exec Start
2026 2026
 
2027 2027
 `POST /exec/(id)/start`
2028 2028
 
... ...
@@ -2063,7 +2061,7 @@ interactive session with the `exec` command.
2063 2063
 
2064 2064
 Similar to the stream behavior of `POST /containers/(id or name)/attach` API
2065 2065
 
2066
-### Exec Resize
2066
+#### Exec Resize
2067 2067
 
2068 2068
 `POST /exec/(id)/resize`
2069 2069
 
... ...
@@ -2090,7 +2088,7 @@ This API is valid only if `tty` was specified as part of creating and starting t
2090 2090
 -   **201** – no error
2091 2091
 -   **404** – no such exec instance
2092 2092
 
2093
-### Exec Inspect
2093
+#### Exec Inspect
2094 2094
 
2095 2095
 `GET /exec/(id)/json`
2096 2096
 
... ...
@@ -2195,9 +2193,9 @@ Return low-level information about the `exec` command `id`.
2195 2195
 -   **404** – no such exec instance
2196 2196
 -   **500** - server error
2197 2197
 
2198
-# 3. Going further
2198
+## 3. Going further
2199 2199
 
2200
-## 3.1 Inside `docker run`
2200
+### 3.1 Inside `docker run`
2201 2201
 
2202 2202
 As an example, the `docker run` command line makes the following API calls:
2203 2203
 
... ...
@@ -2215,7 +2213,7 @@ As an example, the `docker run` command line makes the following API calls:
2215 2215
 
2216 2216
 - If in detached mode or only `stdin` is attached, display the container's id.
2217 2217
 
2218
-## 3.2 Hijacking
2218
+### 3.2 Hijacking
2219 2219
 
2220 2220
 In this version of the API, `/attach`, uses hijacking to transport `stdin`,
2221 2221
 `stdout`, and `stderr` on the same socket.
... ...
@@ -2230,7 +2228,7 @@ When Docker daemon detects the `Upgrade` header, it switches its status code
2230 2230
 from **200 OK** to **101 UPGRADED** and resends the same headers.
2231 2231
 
2232 2232
 
2233
-## 3.3 CORS Requests
2233
+### 3.3 CORS Requests
2234 2234
 
2235 2235
 To set cross origin requests to the Engine API please give values to
2236 2236
 `--api-cors-header` when running Docker in daemon mode. Set * (asterisk) allows all,
... ...
@@ -16,9 +16,7 @@ redirect_from:
16 16
      will be rejected.
17 17
 -->
18 18
 
19
-# Docker Engine API v1.20
20
-
21
-# 1. Brief introduction
19
+## 1. Brief introduction
22 20
 
23 21
  - The daemon listens on `unix:///var/run/docker.sock` but you can
24 22
    [Bind Docker to another host/port or a Unix socket](../commandline/dockerd.md#bind-docker-to-another-host-port-or-a-unix-socket).
... ...
@@ -26,11 +24,11 @@ redirect_from:
26 26
    or `pull`, the HTTP connection is hijacked to transport `stdout`,
27 27
    `stdin` and `stderr`.
28 28
 
29
-# 2. Endpoints
29
+## 2. Endpoints
30 30
 
31
-## 2.1 Containers
31
+### 2.1 Containers
32 32
 
33
-### List containers
33
+#### List containers
34 34
 
35 35
 `GET /containers/json`
36 36
 
... ...
@@ -123,7 +121,7 @@ List containers
123 123
 -   **400** – bad parameter
124 124
 -   **500** – server error
125 125
 
126
-### Create a container
126
+#### Create a container
127 127
 
128 128
 `POST /containers/create`
129 129
 
... ...
@@ -324,7 +322,7 @@ Create a container
324 324
 -   **409** – conflict
325 325
 -   **500** – server error
326 326
 
327
-### Inspect a container
327
+#### Inspect a container
328 328
 
329 329
 `GET /containers/(id or name)/json`
330 330
 
... ...
@@ -466,7 +464,7 @@ Return low-level information on the container `id`
466 466
 -   **404** – no such container
467 467
 -   **500** – server error
468 468
 
469
-### List processes running inside a container
469
+#### List processes running inside a container
470 470
 
471 471
 `GET /containers/(id or name)/top`
472 472
 
... ...
@@ -530,7 +528,7 @@ supported on Windows.
530 530
 -   **404** – no such container
531 531
 -   **500** – server error
532 532
 
533
-### Get container logs
533
+#### Get container logs
534 534
 
535 535
 `GET /containers/(id or name)/logs`
536 536
 
... ...
@@ -572,7 +570,7 @@ Get `stdout` and `stderr` logs from the container ``id``
572 572
 -   **404** – no such container
573 573
 -   **500** – server error
574 574
 
575
-### Inspect changes on a container's filesystem
575
+#### Inspect changes on a container's filesystem
576 576
 
577 577
 `GET /containers/(id or name)/changes`
578 578
 
... ...
@@ -614,7 +612,7 @@ Values for `Kind`:
614 614
 -   **404** – no such container
615 615
 -   **500** – server error
616 616
 
617
-### Export a container
617
+#### Export a container
618 618
 
619 619
 `GET /containers/(id or name)/export`
620 620
 
... ...
@@ -639,7 +637,7 @@ Export the contents of container `id`
639 639
 -   **404** – no such container
640 640
 -   **500** – server error
641 641
 
642
-### Get container stats based on resource usage
642
+#### Get container stats based on resource usage
643 643
 
644 644
 `GET /containers/(id or name)/stats`
645 645
 
... ...
@@ -748,7 +746,7 @@ The precpu_stats is the cpu statistic of last read, which is used for calculatin
748 748
 -   **404** – no such container
749 749
 -   **500** – server error
750 750
 
751
-### Resize a container TTY
751
+#### Resize a container TTY
752 752
 
753 753
 `POST /containers/(id or name)/resize?h=<height>&w=<width>`
754 754
 
... ...
@@ -775,7 +773,7 @@ Resize the TTY for container with  `id`. You must restart the container for the
775 775
 -   **404** – No such container
776 776
 -   **500** – Cannot resize container
777 777
 
778
-### Start a container
778
+#### Start a container
779 779
 
780 780
 `POST /containers/(id or name)/start`
781 781
 
... ...
@@ -800,7 +798,7 @@ Start the container `id`
800 800
 -   **404** – no such container
801 801
 -   **500** – server error
802 802
 
803
-### Stop a container
803
+#### Stop a container
804 804
 
805 805
 `POST /containers/(id or name)/stop`
806 806
 
... ...
@@ -825,7 +823,7 @@ Stop the container `id`
825 825
 -   **404** – no such container
826 826
 -   **500** – server error
827 827
 
828
-### Restart a container
828
+#### Restart a container
829 829
 
830 830
 `POST /containers/(id or name)/restart`
831 831
 
... ...
@@ -849,7 +847,7 @@ Restart the container `id`
849 849
 -   **404** – no such container
850 850
 -   **500** – server error
851 851
 
852
-### Kill a container
852
+#### Kill a container
853 853
 
854 854
 `POST /containers/(id or name)/kill`
855 855
 
... ...
@@ -874,7 +872,7 @@ Kill the container `id`
874 874
 -   **404** – no such container
875 875
 -   **500** – server error
876 876
 
877
-### Rename a container
877
+#### Rename a container
878 878
 
879 879
 `POST /containers/(id or name)/rename`
880 880
 
... ...
@@ -899,7 +897,7 @@ Rename the container `id` to a `new_name`
899 899
 -   **409** - conflict name already assigned
900 900
 -   **500** – server error
901 901
 
902
-### Pause a container
902
+#### Pause a container
903 903
 
904 904
 `POST /containers/(id or name)/pause`
905 905
 
... ...
@@ -919,7 +917,7 @@ Pause the container `id`
919 919
 -   **404** – no such container
920 920
 -   **500** – server error
921 921
 
922
-### Unpause a container
922
+#### Unpause a container
923 923
 
924 924
 `POST /containers/(id or name)/unpause`
925 925
 
... ...
@@ -939,7 +937,7 @@ Unpause the container `id`
939 939
 -   **404** – no such container
940 940
 -   **500** – server error
941 941
 
942
-### Attach to a container
942
+#### Attach to a container
943 943
 
944 944
 `POST /containers/(id or name)/attach`
945 945
 
... ...
@@ -1024,7 +1022,7 @@ The simplest way to implement the Attach protocol is the following:
1024 1024
     4.  Read the extracted size and output it on the correct output.
1025 1025
     5.  Goto 1.
1026 1026
 
1027
-### Attach to a container (websocket)
1027
+#### Attach to a container (websocket)
1028 1028
 
1029 1029
 `GET /containers/(id or name)/attach/ws`
1030 1030
 
... ...
@@ -1061,7 +1059,7 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet
1061 1061
 -   **404** – no such container
1062 1062
 -   **500** – server error
1063 1063
 
1064
-### Wait a container
1064
+#### Wait a container
1065 1065
 
1066 1066
 `POST /containers/(id or name)/wait`
1067 1067
 
... ...
@@ -1084,7 +1082,7 @@ Block until container `id` stops, then returns the exit code
1084 1084
 -   **404** – no such container
1085 1085
 -   **500** – server error
1086 1086
 
1087
-### Remove a container
1087
+#### Remove a container
1088 1088
 
1089 1089
 `DELETE /containers/(id or name)`
1090 1090
 
... ...
@@ -1113,7 +1111,7 @@ Remove the container `id` from the filesystem
1113 1113
 -   **409** – conflict
1114 1114
 -   **500** – server error
1115 1115
 
1116
-### Copy files or folders from a container
1116
+#### Copy files or folders from a container
1117 1117
 
1118 1118
 `POST /containers/(id or name)/copy`
1119 1119
 
... ...
@@ -1145,14 +1143,14 @@ Copy files or folders of container `id`
1145 1145
 -   **404** – no such container
1146 1146
 -   **500** – server error
1147 1147
 
1148
-### Retrieving information about files and folders in a container
1148
+#### Retrieving information about files and folders in a container
1149 1149
 
1150 1150
 `HEAD /containers/(id or name)/archive`
1151 1151
 
1152 1152
 See the description of the `X-Docker-Container-Path-Stat` header in the
1153 1153
 following section.
1154 1154
 
1155
-### Get an archive of a filesystem resource in a container
1155
+#### Get an archive of a filesystem resource in a container
1156 1156
 
1157 1157
 `GET /containers/(id or name)/archive`
1158 1158
 
... ...
@@ -1217,7 +1215,7 @@ desired.
1217 1217
     - no such file or directory (**path** does not exist)
1218 1218
 - **500** - server error
1219 1219
 
1220
-### Extract an archive of files or folders to a directory in a container
1220
+#### Extract an archive of files or folders to a directory in a container
1221 1221
 
1222 1222
 `PUT /containers/(id or name)/archive`
1223 1223
 
... ...
@@ -1265,9 +1263,9 @@ Upload a tar archive to be extracted to a path in the filesystem of container
1265 1265
     - no such file or directory (**path** resource does not exist)
1266 1266
 - **500** – server error
1267 1267
 
1268
-## 2.2 Images
1268
+### 2.2 Images
1269 1269
 
1270
-### List Images
1270
+#### List Images
1271 1271
 
1272 1272
 `GET /images/json`
1273 1273
 
... ...
@@ -1358,7 +1356,7 @@ references on the command line.
1358 1358
   -   `label=key` or `label="key=value"` of an image label
1359 1359
 -   **filter** - only return images with the specified name
1360 1360
 
1361
-### Build image from a Dockerfile
1361
+#### Build image from a Dockerfile
1362 1362
 
1363 1363
 `POST /build`
1364 1364
 
... ...
@@ -1456,7 +1454,7 @@ or being killed.
1456 1456
 -   **200** – no error
1457 1457
 -   **500** – server error
1458 1458
 
1459
-### Create an image
1459
+#### Create an image
1460 1460
 
1461 1461
 `POST /images/create`
1462 1462
 
... ...
@@ -1500,7 +1498,7 @@ a base64-encoded AuthConfig object.
1500 1500
 
1501 1501
 
1502 1502
 
1503
-### Inspect an image
1503
+#### Inspect an image
1504 1504
 
1505 1505
 `GET /images/(name)/json`
1506 1506
 
... ...
@@ -1551,7 +1549,7 @@ Return low-level information on the image `name`
1551 1551
 -   **404** – no such image
1552 1552
 -   **500** – server error
1553 1553
 
1554
-### Get the history of an image
1554
+#### Get the history of an image
1555 1555
 
1556 1556
 `GET /images/(name)/history`
1557 1557
 
... ...
@@ -1605,7 +1603,7 @@ Return the history of the image `name`
1605 1605
 -   **404** – no such image
1606 1606
 -   **500** – server error
1607 1607
 
1608
-### Push an image on the registry
1608
+#### Push an image on the registry
1609 1609
 
1610 1610
 `POST /images/(name)/push`
1611 1611
 
... ...
@@ -1648,7 +1646,7 @@ then be used in the URL. This duplicates the command line's flow.
1648 1648
 -   **404** – no such image
1649 1649
 -   **500** – server error
1650 1650
 
1651
-### Tag an image into a repository
1651
+#### Tag an image into a repository
1652 1652
 
1653 1653
 `POST /images/(name)/tag`
1654 1654
 
... ...
@@ -1676,7 +1674,7 @@ Tag the image `name` into a repository
1676 1676
 -   **409** – conflict
1677 1677
 -   **500** – server error
1678 1678
 
1679
-### Remove an image
1679
+#### Remove an image
1680 1680
 
1681 1681
 `DELETE /images/(name)`
1682 1682
 
... ...
@@ -1709,7 +1707,7 @@ Remove the image `name` from the filesystem
1709 1709
 -   **409** – conflict
1710 1710
 -   **500** – server error
1711 1711
 
1712
-### Search images
1712
+#### Search images
1713 1713
 
1714 1714
 `GET /images/search`
1715 1715
 
... ...
@@ -1762,9 +1760,9 @@ Search for an image on [Docker Hub](https://hub.docker.com).
1762 1762
 -   **200** – no error
1763 1763
 -   **500** – server error
1764 1764
 
1765
-## 2.3 Misc
1765
+### 2.3 Misc
1766 1766
 
1767
-### Check auth configuration
1767
+#### Check auth configuration
1768 1768
 
1769 1769
 `POST /auth`
1770 1770
 
... ...
@@ -1792,7 +1790,7 @@ Get the default username and email
1792 1792
 -   **204** – no error
1793 1793
 -   **500** – server error
1794 1794
 
1795
-### Display system-wide information
1795
+#### Display system-wide information
1796 1796
 
1797 1797
 `GET /info`
1798 1798
 
... ...
@@ -1861,7 +1859,7 @@ Display system-wide information
1861 1861
 -   **200** – no error
1862 1862
 -   **500** – server error
1863 1863
 
1864
-### Show the docker version information
1864
+#### Show the docker version information
1865 1865
 
1866 1866
 `GET /version`
1867 1867
 
... ...
@@ -1892,7 +1890,7 @@ Show the docker version information
1892 1892
 -   **200** – no error
1893 1893
 -   **500** – server error
1894 1894
 
1895
-### Ping the docker server
1895
+#### Ping the docker server
1896 1896
 
1897 1897
 `GET /_ping`
1898 1898
 
... ...
@@ -1914,7 +1912,7 @@ Ping the docker server
1914 1914
 -   **200** - no error
1915 1915
 -   **500** - server error
1916 1916
 
1917
-### Create a new image from a container's changes
1917
+#### Create a new image from a container's changes
1918 1918
 
1919 1919
 `POST /commit`
1920 1920
 
... ...
@@ -1986,7 +1984,7 @@ Create a new image from a container's changes
1986 1986
 -   **404** – no such container
1987 1987
 -   **500** – server error
1988 1988
 
1989
-### Monitor Docker's events
1989
+#### Monitor Docker's events
1990 1990
 
1991 1991
 `GET /events`
1992 1992
 
... ...
@@ -2028,7 +2026,7 @@ Docker images report the following events:
2028 2028
 -   **200** – no error
2029 2029
 -   **500** – server error
2030 2030
 
2031
-### Get a tarball containing all images in a repository
2031
+#### Get a tarball containing all images in a repository
2032 2032
 
2033 2033
 `GET /images/(name)/get`
2034 2034
 
... ...
@@ -2058,7 +2056,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2058 2058
 -   **200** – no error
2059 2059
 -   **500** – server error
2060 2060
 
2061
-### Get a tarball containing all images
2061
+#### Get a tarball containing all images
2062 2062
 
2063 2063
 `GET /images/get`
2064 2064
 
... ...
@@ -2087,7 +2085,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2087 2087
 -   **200** – no error
2088 2088
 -   **500** – server error
2089 2089
 
2090
-### Load a tarball with a set of images and tags into docker
2090
+#### Load a tarball with a set of images and tags into docker
2091 2091
 
2092 2092
 `POST /images/load`
2093 2093
 
... ...
@@ -2110,7 +2108,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2110 2110
 -   **200** – no error
2111 2111
 -   **500** – server error
2112 2112
 
2113
-### Image tarball format
2113
+#### Image tarball format
2114 2114
 
2115 2115
 An image tarball contains one directory per image layer (named using its long ID),
2116 2116
 each containing these files:
... ...
@@ -2131,7 +2129,7 @@ the root that contains a list of repository and tag names mapped to layer IDs.
2131 2131
 }
2132 2132
 ```
2133 2133
 
2134
-### Exec Create
2134
+#### Exec Create
2135 2135
 
2136 2136
 `POST /containers/(id or name)/exec`
2137 2137
 
... ...
@@ -2177,7 +2175,7 @@ Sets up an exec instance in a running container `id`
2177 2177
 -   **201** – no error
2178 2178
 -   **404** – no such container
2179 2179
 
2180
-### Exec Start
2180
+#### Exec Start
2181 2181
 
2182 2182
 `POST /exec/(id)/start`
2183 2183
 
... ...
@@ -2218,7 +2216,7 @@ interactive session with the `exec` command.
2218 2218
 
2219 2219
 Similar to the stream behavior of `POST /containers/(id or name)/attach` API
2220 2220
 
2221
-### Exec Resize
2221
+#### Exec Resize
2222 2222
 
2223 2223
 `POST /exec/(id)/resize`
2224 2224
 
... ...
@@ -2245,7 +2243,7 @@ This API is valid only if `tty` was specified as part of creating and starting t
2245 2245
 -   **201** – no error
2246 2246
 -   **404** – no such exec instance
2247 2247
 
2248
-### Exec Inspect
2248
+#### Exec Inspect
2249 2249
 
2250 2250
 `GET /exec/(id)/json`
2251 2251
 
... ...
@@ -2348,9 +2346,9 @@ Return low-level information about the `exec` command `id`.
2348 2348
 -   **404** – no such exec instance
2349 2349
 -   **500** - server error
2350 2350
 
2351
-# 3. Going further
2351
+## 3. Going further
2352 2352
 
2353
-## 3.1 Inside `docker run`
2353
+### 3.1 Inside `docker run`
2354 2354
 
2355 2355
 As an example, the `docker run` command line makes the following API calls:
2356 2356
 
... ...
@@ -2368,7 +2366,7 @@ As an example, the `docker run` command line makes the following API calls:
2368 2368
 
2369 2369
 - If in detached mode or only `stdin` is attached, display the container's id.
2370 2370
 
2371
-## 3.2 Hijacking
2371
+### 3.2 Hijacking
2372 2372
 
2373 2373
 In this version of the API, `/attach`, uses hijacking to transport `stdin`,
2374 2374
 `stdout`, and `stderr` on the same socket.
... ...
@@ -2383,7 +2381,7 @@ When Docker daemon detects the `Upgrade` header, it switches its status code
2383 2383
 from **200 OK** to **101 UPGRADED** and resends the same headers.
2384 2384
 
2385 2385
 
2386
-## 3.3 CORS Requests
2386
+### 3.3 CORS Requests
2387 2387
 
2388 2388
 To set cross origin requests to the Engine API please give values to
2389 2389
 `--api-cors-header` when running Docker in daemon mode. Set * (asterisk) allows all,
... ...
@@ -16,8 +16,6 @@ redirect_from:
16 16
      will be rejected.
17 17
 -->
18 18
 
19
-# Docker Engine API v1.21
20
-
21 19
 ## 1. Brief introduction
22 20
 
23 21
  - The daemon listens on `unix:///var/run/docker.sock` but you can
... ...
@@ -28,11 +26,11 @@ redirect_from:
28 28
  - When the client API version is newer than the daemon's, these calls return an HTTP
29 29
    `400 Bad Request` error message.
30 30
 
31
-# 2. Endpoints
31
+## 2. Endpoints
32 32
 
33
-## 2.1 Containers
33
+### 2.1 Containers
34 34
 
35
-### List containers
35
+#### List containers
36 36
 
37 37
 `GET /containers/json`
38 38
 
... ...
@@ -129,7 +127,7 @@ List containers
129 129
 -   **400** – bad parameter
130 130
 -   **500** – server error
131 131
 
132
-### Create a container
132
+#### Create a container
133 133
 
134 134
 `POST /containers/create`
135 135
 
... ...
@@ -347,7 +345,7 @@ Create a container
347 347
 -   **409** – conflict
348 348
 -   **500** – server error
349 349
 
350
-### Inspect a container
350
+#### Inspect a container
351 351
 
352 352
 `GET /containers/(id or name)/json`
353 353
 
... ...
@@ -537,7 +535,7 @@ Return low-level information on the container `id`
537 537
 -   **404** – no such container
538 538
 -   **500** – server error
539 539
 
540
-### List processes running inside a container
540
+#### List processes running inside a container
541 541
 
542 542
 `GET /containers/(id or name)/top`
543 543
 
... ...
@@ -601,7 +599,7 @@ supported on Windows.
601 601
 -   **404** – no such container
602 602
 -   **500** – server error
603 603
 
604
-### Get container logs
604
+#### Get container logs
605 605
 
606 606
 `GET /containers/(id or name)/logs`
607 607
 
... ...
@@ -643,7 +641,7 @@ Get `stdout` and `stderr` logs from the container ``id``
643 643
 -   **404** – no such container
644 644
 -   **500** – server error
645 645
 
646
-### Inspect changes on a container's filesystem
646
+#### Inspect changes on a container's filesystem
647 647
 
648 648
 `GET /containers/(id or name)/changes`
649 649
 
... ...
@@ -685,7 +683,7 @@ Values for `Kind`:
685 685
 -   **404** – no such container
686 686
 -   **500** – server error
687 687
 
688
-### Export a container
688
+#### Export a container
689 689
 
690 690
 `GET /containers/(id or name)/export`
691 691
 
... ...
@@ -710,7 +708,7 @@ Export the contents of container `id`
710 710
 -   **404** – no such container
711 711
 -   **500** – server error
712 712
 
713
-### Get container stats based on resource usage
713
+#### Get container stats based on resource usage
714 714
 
715 715
 `GET /containers/(id or name)/stats`
716 716
 
... ...
@@ -831,7 +829,7 @@ The precpu_stats is the cpu statistic of last read, which is used for calculatin
831 831
 -   **404** – no such container
832 832
 -   **500** – server error
833 833
 
834
-### Resize a container TTY
834
+#### Resize a container TTY
835 835
 
836 836
 `POST /containers/(id or name)/resize`
837 837
 
... ...
@@ -858,7 +856,7 @@ Resize the TTY for container with  `id`. The unit is number of characters. You m
858 858
 -   **404** – No such container
859 859
 -   **500** – Cannot resize container
860 860
 
861
-### Start a container
861
+#### Start a container
862 862
 
863 863
 `POST /containers/(id or name)/start`
864 864
 
... ...
@@ -883,7 +881,7 @@ Start the container `id`
883 883
 -   **404** – no such container
884 884
 -   **500** – server error
885 885
 
886
-### Stop a container
886
+#### Stop a container
887 887
 
888 888
 `POST /containers/(id or name)/stop`
889 889
 
... ...
@@ -908,7 +906,7 @@ Stop the container `id`
908 908
 -   **404** – no such container
909 909
 -   **500** – server error
910 910
 
911
-### Restart a container
911
+#### Restart a container
912 912
 
913 913
 `POST /containers/(id or name)/restart`
914 914
 
... ...
@@ -932,7 +930,7 @@ Restart the container `id`
932 932
 -   **404** – no such container
933 933
 -   **500** – server error
934 934
 
935
-### Kill a container
935
+#### Kill a container
936 936
 
937 937
 `POST /containers/(id or name)/kill`
938 938
 
... ...
@@ -957,7 +955,7 @@ Kill the container `id`
957 957
 -   **404** – no such container
958 958
 -   **500** – server error
959 959
 
960
-### Rename a container
960
+#### Rename a container
961 961
 
962 962
 `POST /containers/(id or name)/rename`
963 963
 
... ...
@@ -982,7 +980,7 @@ Rename the container `id` to a `new_name`
982 982
 -   **409** - conflict name already assigned
983 983
 -   **500** – server error
984 984
 
985
-### Pause a container
985
+#### Pause a container
986 986
 
987 987
 `POST /containers/(id or name)/pause`
988 988
 
... ...
@@ -1002,7 +1000,7 @@ Pause the container `id`
1002 1002
 -   **404** – no such container
1003 1003
 -   **500** – server error
1004 1004
 
1005
-### Unpause a container
1005
+#### Unpause a container
1006 1006
 
1007 1007
 `POST /containers/(id or name)/unpause`
1008 1008
 
... ...
@@ -1022,7 +1020,7 @@ Unpause the container `id`
1022 1022
 -   **404** – no such container
1023 1023
 -   **500** – server error
1024 1024
 
1025
-### Attach to a container
1025
+#### Attach to a container
1026 1026
 
1027 1027
 `POST /containers/(id or name)/attach`
1028 1028
 
... ...
@@ -1107,7 +1105,7 @@ The simplest way to implement the Attach protocol is the following:
1107 1107
     4.  Read the extracted size and output it on the correct output.
1108 1108
     5.  Goto 1.
1109 1109
 
1110
-### Attach to a container (websocket)
1110
+#### Attach to a container (websocket)
1111 1111
 
1112 1112
 `GET /containers/(id or name)/attach/ws`
1113 1113
 
... ...
@@ -1144,7 +1142,7 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet
1144 1144
 -   **404** – no such container
1145 1145
 -   **500** – server error
1146 1146
 
1147
-### Wait a container
1147
+#### Wait a container
1148 1148
 
1149 1149
 `POST /containers/(id or name)/wait`
1150 1150
 
... ...
@@ -1167,7 +1165,7 @@ Block until container `id` stops, then returns the exit code
1167 1167
 -   **404** – no such container
1168 1168
 -   **500** – server error
1169 1169
 
1170
-### Remove a container
1170
+#### Remove a container
1171 1171
 
1172 1172
 `DELETE /containers/(id or name)`
1173 1173
 
... ...
@@ -1196,7 +1194,7 @@ Remove the container `id` from the filesystem
1196 1196
 -   **409** – conflict
1197 1197
 -   **500** – server error
1198 1198
 
1199
-### Copy files or folders from a container
1199
+#### Copy files or folders from a container
1200 1200
 
1201 1201
 `POST /containers/(id or name)/copy`
1202 1202
 
... ...
@@ -1228,14 +1226,14 @@ Copy files or folders of container `id`
1228 1228
 -   **404** – no such container
1229 1229
 -   **500** – server error
1230 1230
 
1231
-### Retrieving information about files and folders in a container
1231
+#### Retrieving information about files and folders in a container
1232 1232
 
1233 1233
 `HEAD /containers/(id or name)/archive`
1234 1234
 
1235 1235
 See the description of the `X-Docker-Container-Path-Stat` header in the
1236 1236
 following section.
1237 1237
 
1238
-### Get an archive of a filesystem resource in a container
1238
+#### Get an archive of a filesystem resource in a container
1239 1239
 
1240 1240
 `GET /containers/(id or name)/archive`
1241 1241
 
... ...
@@ -1300,7 +1298,7 @@ desired.
1300 1300
     - no such file or directory (**path** does not exist)
1301 1301
 - **500** - server error
1302 1302
 
1303
-### Extract an archive of files or folders to a directory in a container
1303
+#### Extract an archive of files or folders to a directory in a container
1304 1304
 
1305 1305
 `PUT /containers/(id or name)/archive`
1306 1306
 
... ...
@@ -1348,9 +1346,9 @@ Upload a tar archive to be extracted to a path in the filesystem of container
1348 1348
     - no such file or directory (**path** resource does not exist)
1349 1349
 - **500** – server error
1350 1350
 
1351
-## 2.2 Images
1351
+### 2.2 Images
1352 1352
 
1353
-### List Images
1353
+#### List Images
1354 1354
 
1355 1355
 `GET /images/json`
1356 1356
 
... ...
@@ -1441,7 +1439,7 @@ references on the command line.
1441 1441
   -   `label=key` or `label="key=value"` of an image label
1442 1442
 -   **filter** - only return images with the specified name
1443 1443
 
1444
-### Build image from a Dockerfile
1444
+#### Build image from a Dockerfile
1445 1445
 
1446 1446
 `POST /build`
1447 1447
 
... ...
@@ -1545,7 +1543,7 @@ or being killed.
1545 1545
 -   **200** – no error
1546 1546
 -   **500** – server error
1547 1547
 
1548
-### Create an image
1548
+#### Create an image
1549 1549
 
1550 1550
 `POST /images/create`
1551 1551
 
... ...
@@ -1593,7 +1591,7 @@ a base64-encoded AuthConfig object.
1593 1593
 
1594 1594
 
1595 1595
 
1596
-### Inspect an image
1596
+#### Inspect an image
1597 1597
 
1598 1598
 `GET /images/(name)/json`
1599 1599
 
... ...
@@ -1704,7 +1702,7 @@ Return low-level information on the image `name`
1704 1704
 -   **404** – no such image
1705 1705
 -   **500** – server error
1706 1706
 
1707
-### Get the history of an image
1707
+#### Get the history of an image
1708 1708
 
1709 1709
 `GET /images/(name)/history`
1710 1710
 
... ...
@@ -1758,7 +1756,7 @@ Return the history of the image `name`
1758 1758
 -   **404** – no such image
1759 1759
 -   **500** – server error
1760 1760
 
1761
-### Push an image on the registry
1761
+#### Push an image on the registry
1762 1762
 
1763 1763
 `POST /images/(name)/push`
1764 1764
 
... ...
@@ -1801,7 +1799,7 @@ then be used in the URL. This duplicates the command line's flow.
1801 1801
 -   **404** – no such image
1802 1802
 -   **500** – server error
1803 1803
 
1804
-### Tag an image into a repository
1804
+#### Tag an image into a repository
1805 1805
 
1806 1806
 `POST /images/(name)/tag`
1807 1807
 
... ...
@@ -1829,7 +1827,7 @@ Tag the image `name` into a repository
1829 1829
 -   **409** – conflict
1830 1830
 -   **500** – server error
1831 1831
 
1832
-### Remove an image
1832
+#### Remove an image
1833 1833
 
1834 1834
 `DELETE /images/(name)`
1835 1835
 
... ...
@@ -1862,7 +1860,7 @@ Remove the image `name` from the filesystem
1862 1862
 -   **409** – conflict
1863 1863
 -   **500** – server error
1864 1864
 
1865
-### Search images
1865
+#### Search images
1866 1866
 
1867 1867
 `GET /images/search`
1868 1868
 
... ...
@@ -1915,9 +1913,9 @@ Search for an image on [Docker Hub](https://hub.docker.com).
1915 1915
 -   **200** – no error
1916 1916
 -   **500** – server error
1917 1917
 
1918
-## 2.3 Misc
1918
+### 2.3 Misc
1919 1919
 
1920
-### Check auth configuration
1920
+#### Check auth configuration
1921 1921
 
1922 1922
 `POST /auth`
1923 1923
 
... ...
@@ -1945,7 +1943,7 @@ Get the default username and email
1945 1945
 -   **204** – no error
1946 1946
 -   **500** – server error
1947 1947
 
1948
-### Display system-wide information
1948
+#### Display system-wide information
1949 1949
 
1950 1950
 `GET /info`
1951 1951
 
... ...
@@ -2016,7 +2014,7 @@ Display system-wide information
2016 2016
 -   **200** – no error
2017 2017
 -   **500** – server error
2018 2018
 
2019
-### Show the docker version information
2019
+#### Show the docker version information
2020 2020
 
2021 2021
 `GET /version`
2022 2022
 
... ...
@@ -2047,7 +2045,7 @@ Show the docker version information
2047 2047
 -   **200** – no error
2048 2048
 -   **500** – server error
2049 2049
 
2050
-### Ping the docker server
2050
+#### Ping the docker server
2051 2051
 
2052 2052
 `GET /_ping`
2053 2053
 
... ...
@@ -2069,7 +2067,7 @@ Ping the docker server
2069 2069
 -   **200** - no error
2070 2070
 -   **500** - server error
2071 2071
 
2072
-### Create a new image from a container's changes
2072
+#### Create a new image from a container's changes
2073 2073
 
2074 2074
 `POST /commit`
2075 2075
 
... ...
@@ -2141,7 +2139,7 @@ Create a new image from a container's changes
2141 2141
 -   **404** – no such container
2142 2142
 -   **500** – server error
2143 2143
 
2144
-### Monitor Docker's events
2144
+#### Monitor Docker's events
2145 2145
 
2146 2146
 `GET /events`
2147 2147
 
... ...
@@ -2184,7 +2182,7 @@ Docker images report the following events:
2184 2184
 -   **200** – no error
2185 2185
 -   **500** – server error
2186 2186
 
2187
-### Get a tarball containing all images in a repository
2187
+#### Get a tarball containing all images in a repository
2188 2188
 
2189 2189
 `GET /images/(name)/get`
2190 2190
 
... ...
@@ -2214,7 +2212,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2214 2214
 -   **200** – no error
2215 2215
 -   **500** – server error
2216 2216
 
2217
-### Get a tarball containing all images
2217
+#### Get a tarball containing all images
2218 2218
 
2219 2219
 `GET /images/get`
2220 2220
 
... ...
@@ -2243,7 +2241,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2243 2243
 -   **200** – no error
2244 2244
 -   **500** – server error
2245 2245
 
2246
-### Load a tarball with a set of images and tags into docker
2246
+#### Load a tarball with a set of images and tags into docker
2247 2247
 
2248 2248
 `POST /images/load`
2249 2249
 
... ...
@@ -2266,7 +2264,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2266 2266
 -   **200** – no error
2267 2267
 -   **500** – server error
2268 2268
 
2269
-### Image tarball format
2269
+#### Image tarball format
2270 2270
 
2271 2271
 An image tarball contains one directory per image layer (named using its long ID),
2272 2272
 each containing these files:
... ...
@@ -2287,7 +2285,7 @@ the root that contains a list of repository and tag names mapped to layer IDs.
2287 2287
 }
2288 2288
 ```
2289 2289
 
2290
-### Exec Create
2290
+#### Exec Create
2291 2291
 
2292 2292
 `POST /containers/(id or name)/exec`
2293 2293
 
... ...
@@ -2337,7 +2335,7 @@ Sets up an exec instance in a running container `id`
2337 2337
 -   **409** - container is paused
2338 2338
 -   **500** - server error
2339 2339
 
2340
-### Exec Start
2340
+#### Exec Start
2341 2341
 
2342 2342
 `POST /exec/(id)/start`
2343 2343
 
... ...
@@ -2379,7 +2377,7 @@ interactive session with the `exec` command.
2379 2379
 
2380 2380
 Similar to the stream behavior of `POST /containers/(id or name)/attach` API
2381 2381
 
2382
-### Exec Resize
2382
+#### Exec Resize
2383 2383
 
2384 2384
 `POST /exec/(id)/resize`
2385 2385
 
... ...
@@ -2406,7 +2404,7 @@ This API is valid only if `tty` was specified as part of creating and starting t
2406 2406
 -   **201** – no error
2407 2407
 -   **404** – no such exec instance
2408 2408
 
2409
-### Exec Inspect
2409
+#### Exec Inspect
2410 2410
 
2411 2411
 `GET /exec/(id)/json`
2412 2412
 
... ...
@@ -2532,9 +2530,9 @@ Return low-level information about the `exec` command `id`.
2532 2532
 -   **404** – no such exec instance
2533 2533
 -   **500** - server error
2534 2534
 
2535
-## 2.4 Volumes
2535
+### 2.4 Volumes
2536 2536
 
2537
-### List volumes
2537
+#### List volumes
2538 2538
 
2539 2539
 `GET /volumes`
2540 2540
 
... ...
@@ -2566,7 +2564,7 @@ Return low-level information about the `exec` command `id`.
2566 2566
 -   **200** - no error
2567 2567
 -   **500** - server error
2568 2568
 
2569
-### Create a volume
2569
+#### Create a volume
2570 2570
 
2571 2571
 `POST /volumes/create`
2572 2572
 
... ...
@@ -2604,7 +2602,7 @@ Create a volume
2604 2604
 - **DriverOpts** - A mapping of driver options and values. These options are
2605 2605
     passed directly to the driver and are driver specific.
2606 2606
 
2607
-### Inspect a volume
2607
+#### Inspect a volume
2608 2608
 
2609 2609
 `GET /volumes/(name)`
2610 2610
 
... ...
@@ -2631,7 +2629,7 @@ Return low-level information on the volume `name`
2631 2631
 -   **404** - no such volume
2632 2632
 -   **500** - server error
2633 2633
 
2634
-### Remove a volume
2634
+#### Remove a volume
2635 2635
 
2636 2636
 `DELETE /volumes/(name)`
2637 2637
 
... ...
@@ -2652,9 +2650,9 @@ Instruct the driver to remove the volume (`name`).
2652 2652
 -   **409** - volume is in use and cannot be removed
2653 2653
 -   **500** - server error
2654 2654
 
2655
-## 2.5 Networks
2655
+### 2.5 Networks
2656 2656
 
2657
-### List networks
2657
+#### List networks
2658 2658
 
2659 2659
 `GET /networks`
2660 2660
 
... ...
@@ -2735,7 +2733,7 @@ Content-Type: application/json
2735 2735
 -   **200** - no error
2736 2736
 -   **500** - server error
2737 2737
 
2738
-### Inspect network
2738
+#### Inspect network
2739 2739
 
2740 2740
 `GET /networks/<network-id>`
2741 2741
 
... ...
@@ -2786,7 +2784,7 @@ Content-Type: application/json
2786 2786
 -   **200** - no error
2787 2787
 -   **404** - network not found
2788 2788
 
2789
-### Create a network
2789
+#### Create a network
2790 2790
 
2791 2791
 `POST /networks/create`
2792 2792
 
... ...
@@ -2844,7 +2842,7 @@ Content-Type: application/json
2844 2844
       `{"Subnet": <CIDR>, "IPRange": <CIDR>, "Gateway": <IP address>, "AuxAddress": <device_name:IP address>}`
2845 2845
 - **Options** - Network specific options to be used by the drivers
2846 2846
 
2847
-### Connect a container to a network
2847
+#### Connect a container to a network
2848 2848
 
2849 2849
 `POST /networks/(id)/connect`
2850 2850
 
... ...
@@ -2875,7 +2873,7 @@ Content-Type: application/json
2875 2875
 
2876 2876
 - **container** - container-id/name to be connected to the network
2877 2877
 
2878
-### Disconnect a container from a network
2878
+#### Disconnect a container from a network
2879 2879
 
2880 2880
 `POST /networks/(id)/disconnect`
2881 2881
 
... ...
@@ -2906,7 +2904,7 @@ Content-Type: application/json
2906 2906
 
2907 2907
 - **Container** - container-id/name to be disconnected from a network
2908 2908
 
2909
-### Remove a network
2909
+#### Remove a network
2910 2910
 
2911 2911
 `DELETE /networks/(id)`
2912 2912
 
... ...
@@ -2926,9 +2924,9 @@ Instruct the driver to remove the network (`id`).
2926 2926
 -   **404** - no such network
2927 2927
 -   **500** - server error
2928 2928
 
2929
-# 3. Going further
2929
+## 3. Going further
2930 2930
 
2931
-## 3.1 Inside `docker run`
2931
+### 3.1 Inside `docker run`
2932 2932
 
2933 2933
 As an example, the `docker run` command line makes the following API calls:
2934 2934
 
... ...
@@ -2946,7 +2944,7 @@ As an example, the `docker run` command line makes the following API calls:
2946 2946
 
2947 2947
 - If in detached mode or only `stdin` is attached, display the container's id.
2948 2948
 
2949
-## 3.2 Hijacking
2949
+### 3.2 Hijacking
2950 2950
 
2951 2951
 In this version of the API, `/attach`, uses hijacking to transport `stdin`,
2952 2952
 `stdout`, and `stderr` on the same socket.
... ...
@@ -2961,7 +2959,7 @@ When Docker daemon detects the `Upgrade` header, it switches its status code
2961 2961
 from **200 OK** to **101 UPGRADED** and resends the same headers.
2962 2962
 
2963 2963
 
2964
-## 3.3 CORS Requests
2964
+### 3.3 CORS Requests
2965 2965
 
2966 2966
 To set cross origin requests to the Engine API please give values to
2967 2967
 `--api-cors-header` when running Docker in daemon mode. Set * (asterisk) allows all,
... ...
@@ -16,9 +16,7 @@ redirect_from:
16 16
      will be rejected.
17 17
 -->
18 18
 
19
-# Docker Engine API v1.22
20
-
21
-# 1. Brief introduction
19
+## 1. Brief introduction
22 20
 
23 21
  - The daemon listens on `unix:///var/run/docker.sock` but you can
24 22
    [Bind Docker to another host/port or a Unix socket](../commandline/dockerd.md#bind-docker-to-another-host-port-or-a-unix-socket).
... ...
@@ -26,11 +24,11 @@ redirect_from:
26 26
    or `pull`, the HTTP connection is hijacked to transport `stdout`,
27 27
    `stdin` and `stderr`.
28 28
 
29
-# 2. Endpoints
29
+## 2. Endpoints
30 30
 
31
-## 2.1 Containers
31
+### 2.1 Containers
32 32
 
33
-### List containers
33
+#### List containers
34 34
 
35 35
 `GET /containers/json`
36 36
 
... ...
@@ -212,7 +210,7 @@ List containers
212 212
 -   **400** – bad parameter
213 213
 -   **500** – server error
214 214
 
215
-### Create a container
215
+#### Create a container
216 216
 
217 217
 `POST /containers/create`
218 218
 
... ...
@@ -460,7 +458,7 @@ Create a container
460 460
 -   **409** – conflict
461 461
 -   **500** – server error
462 462
 
463
-### Inspect a container
463
+#### Inspect a container
464 464
 
465 465
 `GET /containers/(id or name)/json`
466 466
 
... ...
@@ -663,7 +661,7 @@ Return low-level information on the container `id`
663 663
 -   **404** – no such container
664 664
 -   **500** – server error
665 665
 
666
-### List processes running inside a container
666
+#### List processes running inside a container
667 667
 
668 668
 `GET /containers/(id or name)/top`
669 669
 
... ...
@@ -727,7 +725,7 @@ supported on Windows.
727 727
 -   **404** – no such container
728 728
 -   **500** – server error
729 729
 
730
-### Get container logs
730
+#### Get container logs
731 731
 
732 732
 `GET /containers/(id or name)/logs`
733 733
 
... ...
@@ -769,7 +767,7 @@ Get `stdout` and `stderr` logs from the container ``id``
769 769
 -   **404** – no such container
770 770
 -   **500** – server error
771 771
 
772
-### Inspect changes on a container's filesystem
772
+#### Inspect changes on a container's filesystem
773 773
 
774 774
 `GET /containers/(id or name)/changes`
775 775
 
... ...
@@ -811,7 +809,7 @@ Values for `Kind`:
811 811
 -   **404** – no such container
812 812
 -   **500** – server error
813 813
 
814
-### Export a container
814
+#### Export a container
815 815
 
816 816
 `GET /containers/(id or name)/export`
817 817
 
... ...
@@ -836,7 +834,7 @@ Export the contents of container `id`
836 836
 -   **404** – no such container
837 837
 -   **500** – server error
838 838
 
839
-### Get container stats based on resource usage
839
+#### Get container stats based on resource usage
840 840
 
841 841
 `GET /containers/(id or name)/stats`
842 842
 
... ...
@@ -957,7 +955,7 @@ The precpu_stats is the cpu statistic of last read, which is used for calculatin
957 957
 -   **404** – no such container
958 958
 -   **500** – server error
959 959
 
960
-### Resize a container TTY
960
+#### Resize a container TTY
961 961
 
962 962
 `POST /containers/(id or name)/resize`
963 963
 
... ...
@@ -984,7 +982,7 @@ Resize the TTY for container with  `id`. The unit is number of characters. You m
984 984
 -   **404** – No such container
985 985
 -   **500** – Cannot resize container
986 986
 
987
-### Start a container
987
+#### Start a container
988 988
 
989 989
 `POST /containers/(id or name)/start`
990 990
 
... ...
@@ -1015,7 +1013,7 @@ Start the container `id`
1015 1015
 -   **404** – no such container
1016 1016
 -   **500** – server error
1017 1017
 
1018
-### Stop a container
1018
+#### Stop a container
1019 1019
 
1020 1020
 `POST /containers/(id or name)/stop`
1021 1021
 
... ...
@@ -1040,7 +1038,7 @@ Stop the container `id`
1040 1040
 -   **404** – no such container
1041 1041
 -   **500** – server error
1042 1042
 
1043
-### Restart a container
1043
+#### Restart a container
1044 1044
 
1045 1045
 `POST /containers/(id or name)/restart`
1046 1046
 
... ...
@@ -1064,7 +1062,7 @@ Restart the container `id`
1064 1064
 -   **404** – no such container
1065 1065
 -   **500** – server error
1066 1066
 
1067
-### Kill a container
1067
+#### Kill a container
1068 1068
 
1069 1069
 `POST /containers/(id or name)/kill`
1070 1070
 
... ...
@@ -1089,7 +1087,7 @@ Kill the container `id`
1089 1089
 -   **404** – no such container
1090 1090
 -   **500** – server error
1091 1091
 
1092
-### Update a container
1092
+#### Update a container
1093 1093
 
1094 1094
 `POST /containers/(id or name)/update`
1095 1095
 
... ...
@@ -1129,7 +1127,7 @@ Update resource configs of one or more containers.
1129 1129
 -   **404** – no such container
1130 1130
 -   **500** – server error
1131 1131
 
1132
-### Rename a container
1132
+#### Rename a container
1133 1133
 
1134 1134
 `POST /containers/(id or name)/rename`
1135 1135
 
... ...
@@ -1154,7 +1152,7 @@ Rename the container `id` to a `new_name`
1154 1154
 -   **409** - conflict name already assigned
1155 1155
 -   **500** – server error
1156 1156
 
1157
-### Pause a container
1157
+#### Pause a container
1158 1158
 
1159 1159
 `POST /containers/(id or name)/pause`
1160 1160
 
... ...
@@ -1174,7 +1172,7 @@ Pause the container `id`
1174 1174
 -   **404** – no such container
1175 1175
 -   **500** – server error
1176 1176
 
1177
-### Unpause a container
1177
+#### Unpause a container
1178 1178
 
1179 1179
 `POST /containers/(id or name)/unpause`
1180 1180
 
... ...
@@ -1194,7 +1192,7 @@ Unpause the container `id`
1194 1194
 -   **404** – no such container
1195 1195
 -   **500** – server error
1196 1196
 
1197
-### Attach to a container
1197
+#### Attach to a container
1198 1198
 
1199 1199
 `POST /containers/(id or name)/attach`
1200 1200
 
... ...
@@ -1283,7 +1281,7 @@ The simplest way to implement the Attach protocol is the following:
1283 1283
     4.  Read the extracted size and output it on the correct output.
1284 1284
     5.  Goto 1.
1285 1285
 
1286
-### Attach to a container (websocket)
1286
+#### Attach to a container (websocket)
1287 1287
 
1288 1288
 `GET /containers/(id or name)/attach/ws`
1289 1289
 
... ...
@@ -1323,7 +1321,7 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet
1323 1323
 -   **404** – no such container
1324 1324
 -   **500** – server error
1325 1325
 
1326
-### Wait a container
1326
+#### Wait a container
1327 1327
 
1328 1328
 `POST /containers/(id or name)/wait`
1329 1329
 
... ...
@@ -1346,7 +1344,7 @@ Block until container `id` stops, then returns the exit code
1346 1346
 -   **404** – no such container
1347 1347
 -   **500** – server error
1348 1348
 
1349
-### Remove a container
1349
+#### Remove a container
1350 1350
 
1351 1351
 `DELETE /containers/(id or name)`
1352 1352
 
... ...
@@ -1375,7 +1373,7 @@ Remove the container `id` from the filesystem
1375 1375
 -   **409** – conflict
1376 1376
 -   **500** – server error
1377 1377
 
1378
-### Copy files or folders from a container
1378
+#### Copy files or folders from a container
1379 1379
 
1380 1380
 `POST /containers/(id or name)/copy`
1381 1381
 
... ...
@@ -1407,14 +1405,14 @@ Copy files or folders of container `id`
1407 1407
 -   **404** – no such container
1408 1408
 -   **500** – server error
1409 1409
 
1410
-### Retrieving information about files and folders in a container
1410
+#### Retrieving information about files and folders in a container
1411 1411
 
1412 1412
 `HEAD /containers/(id or name)/archive`
1413 1413
 
1414 1414
 See the description of the `X-Docker-Container-Path-Stat` header in the
1415 1415
 following section.
1416 1416
 
1417
-### Get an archive of a filesystem resource in a container
1417
+#### Get an archive of a filesystem resource in a container
1418 1418
 
1419 1419
 `GET /containers/(id or name)/archive`
1420 1420
 
... ...
@@ -1479,7 +1477,7 @@ desired.
1479 1479
     - no such file or directory (**path** does not exist)
1480 1480
 - **500** - server error
1481 1481
 
1482
-### Extract an archive of files or folders to a directory in a container
1482
+#### Extract an archive of files or folders to a directory in a container
1483 1483
 
1484 1484
 `PUT /containers/(id or name)/archive`
1485 1485
 
... ...
@@ -1527,9 +1525,9 @@ Upload a tar archive to be extracted to a path in the filesystem of container
1527 1527
     - no such file or directory (**path** resource does not exist)
1528 1528
 - **500** – server error
1529 1529
 
1530
-## 2.2 Images
1530
+### 2.2 Images
1531 1531
 
1532
-### List Images
1532
+#### List Images
1533 1533
 
1534 1534
 `GET /images/json`
1535 1535
 
... ...
@@ -1620,7 +1618,7 @@ references on the command line.
1620 1620
   -   `label=key` or `label="key=value"` of an image label
1621 1621
 -   **filter** - only return images with the specified name
1622 1622
 
1623
-### Build image from a Dockerfile
1623
+#### Build image from a Dockerfile
1624 1624
 
1625 1625
 `POST /build`
1626 1626
 
... ...
@@ -1725,7 +1723,7 @@ or being killed.
1725 1725
 -   **200** – no error
1726 1726
 -   **500** – server error
1727 1727
 
1728
-### Create an image
1728
+#### Create an image
1729 1729
 
1730 1730
 `POST /images/create`
1731 1731
 
... ...
@@ -1791,7 +1789,7 @@ a base64-encoded AuthConfig object.
1791 1791
 
1792 1792
 
1793 1793
 
1794
-### Inspect an image
1794
+#### Inspect an image
1795 1795
 
1796 1796
 `GET /images/(name)/json`
1797 1797
 
... ...
@@ -1902,7 +1900,7 @@ Return low-level information on the image `name`
1902 1902
 -   **404** – no such image
1903 1903
 -   **500** – server error
1904 1904
 
1905
-### Get the history of an image
1905
+#### Get the history of an image
1906 1906
 
1907 1907
 `GET /images/(name)/history`
1908 1908
 
... ...
@@ -1956,7 +1954,7 @@ Return the history of the image `name`
1956 1956
 -   **404** – no such image
1957 1957
 -   **500** – server error
1958 1958
 
1959
-### Push an image on the registry
1959
+#### Push an image on the registry
1960 1960
 
1961 1961
 `POST /images/(name)/push`
1962 1962
 
... ...
@@ -2018,7 +2016,7 @@ The push is cancelled if the HTTP connection is closed.
2018 2018
 -   **404** – no such image
2019 2019
 -   **500** – server error
2020 2020
 
2021
-### Tag an image into a repository
2021
+#### Tag an image into a repository
2022 2022
 
2023 2023
 `POST /images/(name)/tag`
2024 2024
 
... ...
@@ -2046,7 +2044,7 @@ Tag the image `name` into a repository
2046 2046
 -   **409** – conflict
2047 2047
 -   **500** – server error
2048 2048
 
2049
-### Remove an image
2049
+#### Remove an image
2050 2050
 
2051 2051
 `DELETE /images/(name)`
2052 2052
 
... ...
@@ -2079,7 +2077,7 @@ Remove the image `name` from the filesystem
2079 2079
 -   **409** – conflict
2080 2080
 -   **500** – server error
2081 2081
 
2082
-### Search images
2082
+#### Search images
2083 2083
 
2084 2084
 `GET /images/search`
2085 2085
 
... ...
@@ -2132,9 +2130,9 @@ Search for an image on [Docker Hub](https://hub.docker.com).
2132 2132
 -   **200** – no error
2133 2133
 -   **500** – server error
2134 2134
 
2135
-## 2.3 Misc
2135
+### 2.3 Misc
2136 2136
 
2137
-### Check auth configuration
2137
+#### Check auth configuration
2138 2138
 
2139 2139
 `POST /auth`
2140 2140
 
... ...
@@ -2162,7 +2160,7 @@ Get the default username and email
2162 2162
 -   **204** – no error
2163 2163
 -   **500** – server error
2164 2164
 
2165
-### Display system-wide information
2165
+#### Display system-wide information
2166 2166
 
2167 2167
 `GET /info`
2168 2168
 
... ...
@@ -2249,7 +2247,7 @@ Display system-wide information
2249 2249
 -   **200** – no error
2250 2250
 -   **500** – server error
2251 2251
 
2252
-### Show the docker version information
2252
+#### Show the docker version information
2253 2253
 
2254 2254
 `GET /version`
2255 2255
 
... ...
@@ -2281,7 +2279,7 @@ Show the docker version information
2281 2281
 -   **200** – no error
2282 2282
 -   **500** – server error
2283 2283
 
2284
-### Ping the docker server
2284
+#### Ping the docker server
2285 2285
 
2286 2286
 `GET /_ping`
2287 2287
 
... ...
@@ -2303,7 +2301,7 @@ Ping the docker server
2303 2303
 -   **200** - no error
2304 2304
 -   **500** - server error
2305 2305
 
2306
-### Create a new image from a container's changes
2306
+#### Create a new image from a container's changes
2307 2307
 
2308 2308
 `POST /commit`
2309 2309
 
... ...
@@ -2375,7 +2373,7 @@ Create a new image from a container's changes
2375 2375
 -   **404** – no such container
2376 2376
 -   **500** – server error
2377 2377
 
2378
-### Monitor Docker's events
2378
+#### Monitor Docker's events
2379 2379
 
2380 2380
 `GET /events`
2381 2381
 
... ...
@@ -2575,7 +2573,7 @@ Docker networks report the following events:
2575 2575
 -   **200** – no error
2576 2576
 -   **500** – server error
2577 2577
 
2578
-### Get a tarball containing all images in a repository
2578
+#### Get a tarball containing all images in a repository
2579 2579
 
2580 2580
 `GET /images/(name)/get`
2581 2581
 
... ...
@@ -2605,7 +2603,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2605 2605
 -   **200** – no error
2606 2606
 -   **500** – server error
2607 2607
 
2608
-### Get a tarball containing all images
2608
+#### Get a tarball containing all images
2609 2609
 
2610 2610
 `GET /images/get`
2611 2611
 
... ...
@@ -2634,7 +2632,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2634 2634
 -   **200** – no error
2635 2635
 -   **500** – server error
2636 2636
 
2637
-### Load a tarball with a set of images and tags into docker
2637
+#### Load a tarball with a set of images and tags into docker
2638 2638
 
2639 2639
 `POST /images/load`
2640 2640
 
... ...
@@ -2657,7 +2655,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2657 2657
 -   **200** – no error
2658 2658
 -   **500** – server error
2659 2659
 
2660
-### Image tarball format
2660
+#### Image tarball format
2661 2661
 
2662 2662
 An image tarball contains one directory per image layer (named using its long ID),
2663 2663
 each containing these files:
... ...
@@ -2678,7 +2676,7 @@ the root that contains a list of repository and tag names mapped to layer IDs.
2678 2678
 }
2679 2679
 ```
2680 2680
 
2681
-### Exec Create
2681
+#### Exec Create
2682 2682
 
2683 2683
 `POST /containers/(id or name)/exec`
2684 2684
 
... ...
@@ -2732,7 +2730,7 @@ Sets up an exec instance in a running container `id`
2732 2732
 -   **409** - container is paused
2733 2733
 -   **500** - server error
2734 2734
 
2735
-### Exec Start
2735
+#### Exec Start
2736 2736
 
2737 2737
 `POST /exec/(id)/start`
2738 2738
 
... ...
@@ -2774,7 +2772,7 @@ interactive session with the `exec` command.
2774 2774
 
2775 2775
 Similar to the stream behavior of `POST /containers/(id or name)/attach` API
2776 2776
 
2777
-### Exec Resize
2777
+#### Exec Resize
2778 2778
 
2779 2779
 `POST /exec/(id)/resize`
2780 2780
 
... ...
@@ -2801,7 +2799,7 @@ This API is valid only if `tty` was specified as part of creating and starting t
2801 2801
 -   **201** – no error
2802 2802
 -   **404** – no such exec instance
2803 2803
 
2804
-### Exec Inspect
2804
+#### Exec Inspect
2805 2805
 
2806 2806
 `GET /exec/(id)/json`
2807 2807
 
... ...
@@ -2844,9 +2842,9 @@ Return low-level information about the `exec` command `id`.
2844 2844
 -   **404** – no such exec instance
2845 2845
 -   **500** - server error
2846 2846
 
2847
-## 2.4 Volumes
2847
+### 2.4 Volumes
2848 2848
 
2849
-### List volumes
2849
+#### List volumes
2850 2850
 
2851 2851
 `GET /volumes`
2852 2852
 
... ...
@@ -2879,7 +2877,7 @@ Return low-level information about the `exec` command `id`.
2879 2879
 -   **200** - no error
2880 2880
 -   **500** - server error
2881 2881
 
2882
-### Create a volume
2882
+#### Create a volume
2883 2883
 
2884 2884
 `POST /volumes/create`
2885 2885
 
... ...
@@ -2917,7 +2915,7 @@ Create a volume
2917 2917
 - **DriverOpts** - A mapping of driver options and values. These options are
2918 2918
     passed directly to the driver and are driver specific.
2919 2919
 
2920
-### Inspect a volume
2920
+#### Inspect a volume
2921 2921
 
2922 2922
 `GET /volumes/(name)`
2923 2923
 
... ...
@@ -2944,7 +2942,7 @@ Return low-level information on the volume `name`
2944 2944
 -   **404** - no such volume
2945 2945
 -   **500** - server error
2946 2946
 
2947
-### Remove a volume
2947
+#### Remove a volume
2948 2948
 
2949 2949
 `DELETE /volumes/(name)`
2950 2950
 
... ...
@@ -2965,9 +2963,9 @@ Instruct the driver to remove the volume (`name`).
2965 2965
 -   **409** - volume is in use and cannot be removed
2966 2966
 -   **500** - server error
2967 2967
 
2968
-## 2.5 Networks
2968
+### 2.5 Networks
2969 2969
 
2970
-### List networks
2970
+#### List networks
2971 2971
 
2972 2972
 `GET /networks`
2973 2973
 
... ...
@@ -3051,7 +3049,7 @@ Content-Type: application/json
3051 3051
 -   **200** - no error
3052 3052
 -   **500** - server error
3053 3053
 
3054
-### Inspect network
3054
+#### Inspect network
3055 3055
 
3056 3056
 `GET /networks/<network-id>`
3057 3057
 
... ...
@@ -3107,7 +3105,7 @@ Content-Type: application/json
3107 3107
 -   **200** - no error
3108 3108
 -   **404** - network not found
3109 3109
 
3110
-### Create a network
3110
+#### Create a network
3111 3111
 
3112 3112
 `POST /networks/create`
3113 3113
 
... ...
@@ -3174,7 +3172,7 @@ Content-Type: application/json
3174 3174
   - **Options** - Driver-specific options, specified as a map: `{"option":"value" [,"option2":"value2"]}`
3175 3175
 - **Options** - Network specific options to be used by the drivers
3176 3176
 
3177
-### Connect a container to a network
3177
+#### Connect a container to a network
3178 3178
 
3179 3179
 `POST /networks/(id)/connect`
3180 3180
 
... ...
@@ -3211,7 +3209,7 @@ Content-Type: application/json
3211 3211
 
3212 3212
 - **container** - container-id/name to be connected to the network
3213 3213
 
3214
-### Disconnect a container from a network
3214
+#### Disconnect a container from a network
3215 3215
 
3216 3216
 `POST /networks/(id)/disconnect`
3217 3217
 
... ...
@@ -3244,7 +3242,7 @@ Content-Type: application/json
3244 3244
 - **Container** - container-id/name to be disconnected from a network
3245 3245
 - **Force** - Force the container to disconnect from a network
3246 3246
 
3247
-### Remove a network
3247
+#### Remove a network
3248 3248
 
3249 3249
 `DELETE /networks/(id)`
3250 3250
 
... ...
@@ -3264,9 +3262,9 @@ Instruct the driver to remove the network (`id`).
3264 3264
 -   **404** - no such network
3265 3265
 -   **500** - server error
3266 3266
 
3267
-# 3. Going further
3267
+## 3. Going further
3268 3268
 
3269
-## 3.1 Inside `docker run`
3269
+### 3.1 Inside `docker run`
3270 3270
 
3271 3271
 As an example, the `docker run` command line makes the following API calls:
3272 3272
 
... ...
@@ -3284,7 +3282,7 @@ As an example, the `docker run` command line makes the following API calls:
3284 3284
 
3285 3285
 - If in detached mode or only `stdin` is attached, display the container's id.
3286 3286
 
3287
-## 3.2 Hijacking
3287
+### 3.2 Hijacking
3288 3288
 
3289 3289
 In this version of the API, `/attach`, uses hijacking to transport `stdin`,
3290 3290
 `stdout`, and `stderr` on the same socket.
... ...
@@ -3299,7 +3297,7 @@ When Docker daemon detects the `Upgrade` header, it switches its status code
3299 3299
 from **200 OK** to **101 UPGRADED** and resends the same headers.
3300 3300
 
3301 3301
 
3302
-## 3.3 CORS Requests
3302
+### 3.3 CORS Requests
3303 3303
 
3304 3304
 To set cross origin requests to the Engine API please give values to
3305 3305
 `--api-cors-header` when running Docker in daemon mode. Set * (asterisk) allows all,
... ...
@@ -16,8 +16,6 @@ redirect_from:
16 16
      will be rejected.
17 17
 -->
18 18
 
19
-# Docker Engine API v1.23
20
-
21 19
 ## 1. Brief introduction
22 20
 
23 21
  - The daemon listens on `unix:///var/run/docker.sock` but you can
... ...
@@ -28,11 +26,11 @@ redirect_from:
28 28
  - When the client API version is newer than the daemon's, these calls return an HTTP
29 29
    `400 Bad Request` error message.
30 30
 
31
-# 2. Endpoints
31
+## 2. Endpoints
32 32
 
33
-## 2.1 Containers
33
+### 2.1 Containers
34 34
 
35
-### List containers
35
+#### List containers
36 36
 
37 37
 `GET /containers/json`
38 38
 
... ...
@@ -236,7 +234,7 @@ List containers
236 236
 -   **400** – bad parameter
237 237
 -   **500** – server error
238 238
 
239
-### Create a container
239
+#### Create a container
240 240
 
241 241
 `POST /containers/create`
242 242
 
... ...
@@ -488,7 +486,7 @@ Create a container
488 488
 -   **409** – conflict
489 489
 -   **500** – server error
490 490
 
491
-### Inspect a container
491
+#### Inspect a container
492 492
 
493 493
 `GET /containers/(id or name)/json`
494 494
 
... ...
@@ -691,7 +689,7 @@ Return low-level information on the container `id`
691 691
 -   **404** – no such container
692 692
 -   **500** – server error
693 693
 
694
-### List processes running inside a container
694
+#### List processes running inside a container
695 695
 
696 696
 `GET /containers/(id or name)/top`
697 697
 
... ...
@@ -755,7 +753,7 @@ supported on Windows.
755 755
 -   **404** – no such container
756 756
 -   **500** – server error
757 757
 
758
-### Get container logs
758
+#### Get container logs
759 759
 
760 760
 `GET /containers/(id or name)/logs`
761 761
 
... ...
@@ -797,7 +795,7 @@ Get `stdout` and `stderr` logs from the container ``id``
797 797
 -   **404** – no such container
798 798
 -   **500** – server error
799 799
 
800
-### Inspect changes on a container's filesystem
800
+#### Inspect changes on a container's filesystem
801 801
 
802 802
 `GET /containers/(id or name)/changes`
803 803
 
... ...
@@ -839,7 +837,7 @@ Values for `Kind`:
839 839
 -   **404** – no such container
840 840
 -   **500** – server error
841 841
 
842
-### Export a container
842
+#### Export a container
843 843
 
844 844
 `GET /containers/(id or name)/export`
845 845
 
... ...
@@ -864,7 +862,7 @@ Export the contents of container `id`
864 864
 -   **404** – no such container
865 865
 -   **500** – server error
866 866
 
867
-### Get container stats based on resource usage
867
+#### Get container stats based on resource usage
868 868
 
869 869
 `GET /containers/(id or name)/stats`
870 870
 
... ...
@@ -988,7 +986,7 @@ The precpu_stats is the cpu statistic of last read, which is used for calculatin
988 988
 -   **404** – no such container
989 989
 -   **500** – server error
990 990
 
991
-### Resize a container TTY
991
+#### Resize a container TTY
992 992
 
993 993
 `POST /containers/(id or name)/resize`
994 994
 
... ...
@@ -1015,7 +1013,7 @@ Resize the TTY for container with  `id`. The unit is number of characters. You m
1015 1015
 -   **404** – No such container
1016 1016
 -   **500** – Cannot resize container
1017 1017
 
1018
-### Start a container
1018
+#### Start a container
1019 1019
 
1020 1020
 `POST /containers/(id or name)/start`
1021 1021
 
... ...
@@ -1046,7 +1044,7 @@ Start the container `id`
1046 1046
 -   **404** – no such container
1047 1047
 -   **500** – server error
1048 1048
 
1049
-### Stop a container
1049
+#### Stop a container
1050 1050
 
1051 1051
 `POST /containers/(id or name)/stop`
1052 1052
 
... ...
@@ -1071,7 +1069,7 @@ Stop the container `id`
1071 1071
 -   **404** – no such container
1072 1072
 -   **500** – server error
1073 1073
 
1074
-### Restart a container
1074
+#### Restart a container
1075 1075
 
1076 1076
 `POST /containers/(id or name)/restart`
1077 1077
 
... ...
@@ -1095,7 +1093,7 @@ Restart the container `id`
1095 1095
 -   **404** – no such container
1096 1096
 -   **500** – server error
1097 1097
 
1098
-### Kill a container
1098
+#### Kill a container
1099 1099
 
1100 1100
 `POST /containers/(id or name)/kill`
1101 1101
 
... ...
@@ -1120,7 +1118,7 @@ Kill the container `id`
1120 1120
 -   **404** – no such container
1121 1121
 -   **500** – server error
1122 1122
 
1123
-### Update a container
1123
+#### Update a container
1124 1124
 
1125 1125
 `POST /containers/(id or name)/update`
1126 1126
 
... ...
@@ -1164,7 +1162,7 @@ Update configuration of one or more containers.
1164 1164
 -   **404** – no such container
1165 1165
 -   **500** – server error
1166 1166
 
1167
-### Rename a container
1167
+#### Rename a container
1168 1168
 
1169 1169
 `POST /containers/(id or name)/rename`
1170 1170
 
... ...
@@ -1189,7 +1187,7 @@ Rename the container `id` to a `new_name`
1189 1189
 -   **409** - conflict name already assigned
1190 1190
 -   **500** – server error
1191 1191
 
1192
-### Pause a container
1192
+#### Pause a container
1193 1193
 
1194 1194
 `POST /containers/(id or name)/pause`
1195 1195
 
... ...
@@ -1209,7 +1207,7 @@ Pause the container `id`
1209 1209
 -   **404** – no such container
1210 1210
 -   **500** – server error
1211 1211
 
1212
-### Unpause a container
1212
+#### Unpause a container
1213 1213
 
1214 1214
 `POST /containers/(id or name)/unpause`
1215 1215
 
... ...
@@ -1229,7 +1227,7 @@ Unpause the container `id`
1229 1229
 -   **404** – no such container
1230 1230
 -   **500** – server error
1231 1231
 
1232
-### Attach to a container
1232
+#### Attach to a container
1233 1233
 
1234 1234
 `POST /containers/(id or name)/attach`
1235 1235
 
... ...
@@ -1318,7 +1316,7 @@ The simplest way to implement the Attach protocol is the following:
1318 1318
     4.  Read the extracted size and output it on the correct output.
1319 1319
     5.  Goto 1.
1320 1320
 
1321
-### Attach to a container (websocket)
1321
+#### Attach to a container (websocket)
1322 1322
 
1323 1323
 `GET /containers/(id or name)/attach/ws`
1324 1324
 
... ...
@@ -1358,7 +1356,7 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet
1358 1358
 -   **404** – no such container
1359 1359
 -   **500** – server error
1360 1360
 
1361
-### Wait a container
1361
+#### Wait a container
1362 1362
 
1363 1363
 `POST /containers/(id or name)/wait`
1364 1364
 
... ...
@@ -1381,7 +1379,7 @@ Block until container `id` stops, then returns the exit code
1381 1381
 -   **404** – no such container
1382 1382
 -   **500** – server error
1383 1383
 
1384
-### Remove a container
1384
+#### Remove a container
1385 1385
 
1386 1386
 `DELETE /containers/(id or name)`
1387 1387
 
... ...
@@ -1410,7 +1408,7 @@ Remove the container `id` from the filesystem
1410 1410
 -   **409** – conflict
1411 1411
 -   **500** – server error
1412 1412
 
1413
-### Copy files or folders from a container
1413
+#### Copy files or folders from a container
1414 1414
 
1415 1415
 `POST /containers/(id or name)/copy`
1416 1416
 
... ...
@@ -1442,14 +1440,14 @@ Copy files or folders of container `id`
1442 1442
 -   **404** – no such container
1443 1443
 -   **500** – server error
1444 1444
 
1445
-### Retrieving information about files and folders in a container
1445
+#### Retrieving information about files and folders in a container
1446 1446
 
1447 1447
 `HEAD /containers/(id or name)/archive`
1448 1448
 
1449 1449
 See the description of the `X-Docker-Container-Path-Stat` header in the
1450 1450
 following section.
1451 1451
 
1452
-### Get an archive of a filesystem resource in a container
1452
+#### Get an archive of a filesystem resource in a container
1453 1453
 
1454 1454
 `GET /containers/(id or name)/archive`
1455 1455
 
... ...
@@ -1514,7 +1512,7 @@ desired.
1514 1514
     - no such file or directory (**path** does not exist)
1515 1515
 - **500** - server error
1516 1516
 
1517
-### Extract an archive of files or folders to a directory in a container
1517
+#### Extract an archive of files or folders to a directory in a container
1518 1518
 
1519 1519
 `PUT /containers/(id or name)/archive`
1520 1520
 
... ...
@@ -1562,9 +1560,9 @@ Upload a tar archive to be extracted to a path in the filesystem of container
1562 1562
     - no such file or directory (**path** resource does not exist)
1563 1563
 - **500** – server error
1564 1564
 
1565
-## 2.2 Images
1565
+### 2.2 Images
1566 1566
 
1567
-### List Images
1567
+#### List Images
1568 1568
 
1569 1569
 `GET /images/json`
1570 1570
 
... ...
@@ -1655,7 +1653,7 @@ references on the command line.
1655 1655
   -   `label=key` or `label="key=value"` of an image label
1656 1656
 -   **filter** - only return images with the specified name
1657 1657
 
1658
-### Build image from a Dockerfile
1658
+#### Build image from a Dockerfile
1659 1659
 
1660 1660
 `POST /build`
1661 1661
 
... ...
@@ -1761,7 +1759,7 @@ or being killed.
1761 1761
 -   **200** – no error
1762 1762
 -   **500** – server error
1763 1763
 
1764
-### Create an image
1764
+#### Create an image
1765 1765
 
1766 1766
 `POST /images/create`
1767 1767
 
... ...
@@ -1827,7 +1825,7 @@ a base64-encoded AuthConfig object.
1827 1827
 
1828 1828
 
1829 1829
 
1830
-### Inspect an image
1830
+#### Inspect an image
1831 1831
 
1832 1832
 `GET /images/(name)/json`
1833 1833
 
... ...
@@ -1945,7 +1943,7 @@ Return low-level information on the image `name`
1945 1945
 -   **404** – no such image
1946 1946
 -   **500** – server error
1947 1947
 
1948
-### Get the history of an image
1948
+#### Get the history of an image
1949 1949
 
1950 1950
 `GET /images/(name)/history`
1951 1951
 
... ...
@@ -1999,7 +1997,7 @@ Return the history of the image `name`
1999 1999
 -   **404** – no such image
2000 2000
 -   **500** – server error
2001 2001
 
2002
-### Push an image on the registry
2002
+#### Push an image on the registry
2003 2003
 
2004 2004
 `POST /images/(name)/push`
2005 2005
 
... ...
@@ -2061,7 +2059,7 @@ The push is cancelled if the HTTP connection is closed.
2061 2061
 -   **404** – no such image
2062 2062
 -   **500** – server error
2063 2063
 
2064
-### Tag an image into a repository
2064
+#### Tag an image into a repository
2065 2065
 
2066 2066
 `POST /images/(name)/tag`
2067 2067
 
... ...
@@ -2089,7 +2087,7 @@ Tag the image `name` into a repository
2089 2089
 -   **409** – conflict
2090 2090
 -   **500** – server error
2091 2091
 
2092
-### Remove an image
2092
+#### Remove an image
2093 2093
 
2094 2094
 `DELETE /images/(name)`
2095 2095
 
... ...
@@ -2122,7 +2120,7 @@ Remove the image `name` from the filesystem
2122 2122
 -   **409** – conflict
2123 2123
 -   **500** – server error
2124 2124
 
2125
-### Search images
2125
+#### Search images
2126 2126
 
2127 2127
 `GET /images/search`
2128 2128
 
... ...
@@ -2175,9 +2173,9 @@ Search for an image on [Docker Hub](https://hub.docker.com).
2175 2175
 -   **200** – no error
2176 2176
 -   **500** – server error
2177 2177
 
2178
-## 2.3 Misc
2178
+### 2.3 Misc
2179 2179
 
2180
-### Check auth configuration
2180
+#### Check auth configuration
2181 2181
 
2182 2182
 `POST /auth`
2183 2183
 
... ...
@@ -2210,7 +2208,7 @@ if available, for accessing the registry without password.
2210 2210
 -   **204** – no error
2211 2211
 -   **500** – server error
2212 2212
 
2213
-### Display system-wide information
2213
+#### Display system-wide information
2214 2214
 
2215 2215
 `GET /info`
2216 2216
 
... ...
@@ -2299,7 +2297,7 @@ Display system-wide information
2299 2299
 -   **200** – no error
2300 2300
 -   **500** – server error
2301 2301
 
2302
-### Show the docker version information
2302
+#### Show the docker version information
2303 2303
 
2304 2304
 `GET /version`
2305 2305
 
... ...
@@ -2331,7 +2329,7 @@ Show the docker version information
2331 2331
 -   **200** – no error
2332 2332
 -   **500** – server error
2333 2333
 
2334
-### Ping the docker server
2334
+#### Ping the docker server
2335 2335
 
2336 2336
 `GET /_ping`
2337 2337
 
... ...
@@ -2353,7 +2351,7 @@ Ping the docker server
2353 2353
 -   **200** - no error
2354 2354
 -   **500** - server error
2355 2355
 
2356
-### Create a new image from a container's changes
2356
+#### Create a new image from a container's changes
2357 2357
 
2358 2358
 `POST /commit`
2359 2359
 
... ...
@@ -2425,7 +2423,7 @@ Create a new image from a container's changes
2425 2425
 -   **404** – no such container
2426 2426
 -   **500** – server error
2427 2427
 
2428
-### Monitor Docker's events
2428
+#### Monitor Docker's events
2429 2429
 
2430 2430
 `GET /events`
2431 2431
 
... ...
@@ -2625,7 +2623,7 @@ Docker networks report the following events:
2625 2625
 -   **200** – no error
2626 2626
 -   **500** – server error
2627 2627
 
2628
-### Get a tarball containing all images in a repository
2628
+#### Get a tarball containing all images in a repository
2629 2629
 
2630 2630
 `GET /images/(name)/get`
2631 2631
 
... ...
@@ -2655,7 +2653,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2655 2655
 -   **200** – no error
2656 2656
 -   **500** – server error
2657 2657
 
2658
-### Get a tarball containing all images
2658
+#### Get a tarball containing all images
2659 2659
 
2660 2660
 `GET /images/get`
2661 2661
 
... ...
@@ -2684,7 +2682,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2684 2684
 -   **200** – no error
2685 2685
 -   **500** – server error
2686 2686
 
2687
-### Load a tarball with a set of images and tags into docker
2687
+#### Load a tarball with a set of images and tags into docker
2688 2688
 
2689 2689
 `POST /images/load`
2690 2690
 
... ...
@@ -2733,7 +2731,7 @@ action completes.
2733 2733
 -   **200** – no error
2734 2734
 -   **500** – server error
2735 2735
 
2736
-### Image tarball format
2736
+#### Image tarball format
2737 2737
 
2738 2738
 An image tarball contains one directory per image layer (named using its long ID),
2739 2739
 each containing these files:
... ...
@@ -2754,7 +2752,7 @@ the root that contains a list of repository and tag names mapped to layer IDs.
2754 2754
 }
2755 2755
 ```
2756 2756
 
2757
-### Exec Create
2757
+#### Exec Create
2758 2758
 
2759 2759
 `POST /containers/(id or name)/exec`
2760 2760
 
... ...
@@ -2808,7 +2806,7 @@ Sets up an exec instance in a running container `id`
2808 2808
 -   **409** - container is paused
2809 2809
 -   **500** - server error
2810 2810
 
2811
-### Exec Start
2811
+#### Exec Start
2812 2812
 
2813 2813
 `POST /exec/(id)/start`
2814 2814
 
... ...
@@ -2850,7 +2848,7 @@ interactive session with the `exec` command.
2850 2850
 
2851 2851
 Similar to the stream behavior of `POST /containers/(id or name)/attach` API
2852 2852
 
2853
-### Exec Resize
2853
+#### Exec Resize
2854 2854
 
2855 2855
 `POST /exec/(id)/resize`
2856 2856
 
... ...
@@ -2877,7 +2875,7 @@ This API is valid only if `tty` was specified as part of creating and starting t
2877 2877
 -   **201** – no error
2878 2878
 -   **404** – no such exec instance
2879 2879
 
2880
-### Exec Inspect
2880
+#### Exec Inspect
2881 2881
 
2882 2882
 `GET /exec/(id)/json`
2883 2883
 
... ...
@@ -2920,9 +2918,9 @@ Return low-level information about the `exec` command `id`.
2920 2920
 -   **404** – no such exec instance
2921 2921
 -   **500** - server error
2922 2922
 
2923
-## 2.4 Volumes
2923
+### 2.4 Volumes
2924 2924
 
2925
-### List volumes
2925
+#### List volumes
2926 2926
 
2927 2927
 `GET /volumes`
2928 2928
 
... ...
@@ -2955,7 +2953,7 @@ Return low-level information about the `exec` command `id`.
2955 2955
 -   **200** - no error
2956 2956
 -   **500** - server error
2957 2957
 
2958
-### Create a volume
2958
+#### Create a volume
2959 2959
 
2960 2960
 `POST /volumes/create`
2961 2961
 
... ...
@@ -3002,7 +3000,7 @@ Create a volume
3002 3002
     passed directly to the driver and are driver specific.
3003 3003
 - **Labels** - Labels to set on the volume, specified as a map: `{"key":"value","key2":"value2"}`
3004 3004
 
3005
-### Inspect a volume
3005
+#### Inspect a volume
3006 3006
 
3007 3007
 `GET /volumes/(name)`
3008 3008
 
... ...
@@ -3033,7 +3031,7 @@ Return low-level information on the volume `name`
3033 3033
 -   **404** - no such volume
3034 3034
 -   **500** - server error
3035 3035
 
3036
-### Remove a volume
3036
+#### Remove a volume
3037 3037
 
3038 3038
 `DELETE /volumes/(name)`
3039 3039
 
... ...
@@ -3054,9 +3052,9 @@ Instruct the driver to remove the volume (`name`).
3054 3054
 -   **409** - volume is in use and cannot be removed
3055 3055
 -   **500** - server error
3056 3056
 
3057
-## 3.5 Networks
3057
+### 3.5 Networks
3058 3058
 
3059
-### List networks
3059
+#### List networks
3060 3060
 
3061 3061
 `GET /networks`
3062 3062
 
... ...
@@ -3146,7 +3144,7 @@ Content-Type: application/json
3146 3146
 -   **200** - no error
3147 3147
 -   **500** - server error
3148 3148
 
3149
-### Inspect network
3149
+#### Inspect network
3150 3150
 
3151 3151
 `GET /networks/<network-id>`
3152 3152
 
... ...
@@ -3208,7 +3206,7 @@ Content-Type: application/json
3208 3208
 -   **200** - no error
3209 3209
 -   **404** - network not found
3210 3210
 
3211
-### Create a network
3211
+#### Create a network
3212 3212
 
3213 3213
 `POST /networks/create`
3214 3214
 
... ...
@@ -3291,7 +3289,7 @@ Content-Type: application/json
3291 3291
 - **Options** - Network specific options to be used by the drivers
3292 3292
 - **Labels** - Labels to set on the network, specified as a map: `{"key":"value" [,"key2":"value2"]}`
3293 3293
 
3294
-### Connect a container to a network
3294
+#### Connect a container to a network
3295 3295
 
3296 3296
 `POST /networks/(id)/connect`
3297 3297
 
... ...
@@ -3328,7 +3326,7 @@ Content-Type: application/json
3328 3328
 
3329 3329
 - **container** - container-id/name to be connected to the network
3330 3330
 
3331
-### Disconnect a container from a network
3331
+#### Disconnect a container from a network
3332 3332
 
3333 3333
 `POST /networks/(id)/disconnect`
3334 3334
 
... ...
@@ -3361,7 +3359,7 @@ Content-Type: application/json
3361 3361
 - **Container** - container-id/name to be disconnected from a network
3362 3362
 - **Force** - Force the container to disconnect from a network
3363 3363
 
3364
-### Remove a network
3364
+#### Remove a network
3365 3365
 
3366 3366
 `DELETE /networks/(id)`
3367 3367
 
... ...
@@ -3381,9 +3379,9 @@ Instruct the driver to remove the network (`id`).
3381 3381
 -   **404** - no such network
3382 3382
 -   **500** - server error
3383 3383
 
3384
-# 3. Going further
3384
+## 3. Going further
3385 3385
 
3386
-## 3.1 Inside `docker run`
3386
+### 3.1 Inside `docker run`
3387 3387
 
3388 3388
 As an example, the `docker run` command line makes the following API calls:
3389 3389
 
... ...
@@ -3401,7 +3399,7 @@ As an example, the `docker run` command line makes the following API calls:
3401 3401
 
3402 3402
 - If in detached mode or only `stdin` is attached, display the container's id.
3403 3403
 
3404
-## 3.2 Hijacking
3404
+### 3.2 Hijacking
3405 3405
 
3406 3406
 In this version of the API, `/attach`, uses hijacking to transport `stdin`,
3407 3407
 `stdout`, and `stderr` on the same socket.
... ...
@@ -3416,7 +3414,7 @@ When Docker daemon detects the `Upgrade` header, it switches its status code
3416 3416
 from **200 OK** to **101 UPGRADED** and resends the same headers.
3417 3417
 
3418 3418
 
3419
-## 3.3 CORS Requests
3419
+### 3.3 CORS Requests
3420 3420
 
3421 3421
 To set cross origin requests to the Engine API please give values to
3422 3422
 `--api-cors-header` when running Docker in daemon mode. Set * (asterisk) allows all,
... ...
@@ -16,9 +16,7 @@ redirect_from:
16 16
      will be rejected.
17 17
 -->
18 18
 
19
-# Docker Engine API v1.24
20
-
21
-# 1. Brief introduction
19
+## 1. Brief introduction
22 20
 
23 21
  - The daemon listens on `unix:///var/run/docker.sock` but you can
24 22
    [Bind Docker to another host/port or a Unix socket](../commandline/dockerd.md#bind-docker-to-another-host-port-or-a-unix-socket).
... ...
@@ -26,7 +24,7 @@ redirect_from:
26 26
    or `pull`, the HTTP connection is hijacked to transport `stdout`,
27 27
    `stdin` and `stderr`.
28 28
 
29
-# 2. Errors
29
+## 2. Errors
30 30
 
31 31
 The Engine API uses standard HTTP status codes to indicate the success or failure of the API call. The body of the response will be JSON in the following format:
32 32
 
... ...
@@ -36,11 +34,11 @@ The Engine API uses standard HTTP status codes to indicate the success or failur
36 36
 
37 37
 The status codes that are returned for each endpoint are specified in the endpoint documentation below.
38 38
 
39
-# 3. Endpoints
39
+## 3. Endpoints
40 40
 
41
-## 3.1 Containers
41
+### 3.1 Containers
42 42
 
43
-### List containers
43
+#### List containers
44 44
 
45 45
 `GET /containers/json`
46 46
 
... ...
@@ -245,7 +243,7 @@ List containers
245 245
 -   **400** – bad parameter
246 246
 -   **500** – server error
247 247
 
248
-### Create a container
248
+#### Create a container
249 249
 
250 250
 `POST /containers/create`
251 251
 
... ...
@@ -511,7 +509,7 @@ Create a container
511 511
 -   **409** – conflict
512 512
 -   **500** – server error
513 513
 
514
-### Inspect a container
514
+#### Inspect a container
515 515
 
516 516
 `GET /containers/(id or name)/json`
517 517
 
... ...
@@ -721,7 +719,7 @@ Return low-level information on the container `id`
721 721
 -   **404** – no such container
722 722
 -   **500** – server error
723 723
 
724
-### List processes running inside a container
724
+#### List processes running inside a container
725 725
 
726 726
 `GET /containers/(id or name)/top`
727 727
 
... ...
@@ -785,7 +783,7 @@ supported on Windows.
785 785
 -   **404** – no such container
786 786
 -   **500** – server error
787 787
 
788
-### Get container logs
788
+#### Get container logs
789 789
 
790 790
 `GET /containers/(id or name)/logs`
791 791
 
... ...
@@ -828,7 +826,7 @@ Get `stdout` and `stderr` logs from the container ``id``
828 828
 -   **404** – no such container
829 829
 -   **500** – server error
830 830
 
831
-### Inspect changes on a container's filesystem
831
+#### Inspect changes on a container's filesystem
832 832
 
833 833
 `GET /containers/(id or name)/changes`
834 834
 
... ...
@@ -870,7 +868,7 @@ Values for `Kind`:
870 870
 -   **404** – no such container
871 871
 -   **500** – server error
872 872
 
873
-### Export a container
873
+#### Export a container
874 874
 
875 875
 `GET /containers/(id or name)/export`
876 876
 
... ...
@@ -895,7 +893,7 @@ Export the contents of container `id`
895 895
 -   **404** – no such container
896 896
 -   **500** – server error
897 897
 
898
-### Get container stats based on resource usage
898
+#### Get container stats based on resource usage
899 899
 
900 900
 `GET /containers/(id or name)/stats`
901 901
 
... ...
@@ -1019,7 +1017,7 @@ The precpu_stats is the cpu statistic of last read, which is used for calculatin
1019 1019
 -   **404** – no such container
1020 1020
 -   **500** – server error
1021 1021
 
1022
-### Resize a container TTY
1022
+#### Resize a container TTY
1023 1023
 
1024 1024
 `POST /containers/(id or name)/resize`
1025 1025
 
... ...
@@ -1046,7 +1044,7 @@ Resize the TTY for container with  `id`. The unit is number of characters. You m
1046 1046
 -   **404** – No such container
1047 1047
 -   **500** – Cannot resize container
1048 1048
 
1049
-### Start a container
1049
+#### Start a container
1050 1050
 
1051 1051
 `POST /containers/(id or name)/start`
1052 1052
 
... ...
@@ -1073,7 +1071,7 @@ Start the container `id`
1073 1073
 -   **404** – no such container
1074 1074
 -   **500** – server error
1075 1075
 
1076
-### Stop a container
1076
+#### Stop a container
1077 1077
 
1078 1078
 `POST /containers/(id or name)/stop`
1079 1079
 
... ...
@@ -1098,7 +1096,7 @@ Stop the container `id`
1098 1098
 -   **404** – no such container
1099 1099
 -   **500** – server error
1100 1100
 
1101
-### Restart a container
1101
+#### Restart a container
1102 1102
 
1103 1103
 `POST /containers/(id or name)/restart`
1104 1104
 
... ...
@@ -1122,7 +1120,7 @@ Restart the container `id`
1122 1122
 -   **404** – no such container
1123 1123
 -   **500** – server error
1124 1124
 
1125
-### Kill a container
1125
+#### Kill a container
1126 1126
 
1127 1127
 `POST /containers/(id or name)/kill`
1128 1128
 
... ...
@@ -1147,7 +1145,7 @@ Kill the container `id`
1147 1147
 -   **404** – no such container
1148 1148
 -   **500** – server error
1149 1149
 
1150
-### Update a container
1150
+#### Update a container
1151 1151
 
1152 1152
 `POST /containers/(id or name)/update`
1153 1153
 
... ...
@@ -1191,7 +1189,7 @@ Update configuration of one or more containers.
1191 1191
 -   **404** – no such container
1192 1192
 -   **500** – server error
1193 1193
 
1194
-### Rename a container
1194
+#### Rename a container
1195 1195
 
1196 1196
 `POST /containers/(id or name)/rename`
1197 1197
 
... ...
@@ -1216,7 +1214,7 @@ Rename the container `id` to a `new_name`
1216 1216
 -   **409** - conflict name already assigned
1217 1217
 -   **500** – server error
1218 1218
 
1219
-### Pause a container
1219
+#### Pause a container
1220 1220
 
1221 1221
 `POST /containers/(id or name)/pause`
1222 1222
 
... ...
@@ -1236,7 +1234,7 @@ Pause the container `id`
1236 1236
 -   **404** – no such container
1237 1237
 -   **500** – server error
1238 1238
 
1239
-### Unpause a container
1239
+#### Unpause a container
1240 1240
 
1241 1241
 `POST /containers/(id or name)/unpause`
1242 1242
 
... ...
@@ -1256,7 +1254,7 @@ Unpause the container `id`
1256 1256
 -   **404** – no such container
1257 1257
 -   **500** – server error
1258 1258
 
1259
-### Attach to a container
1259
+#### Attach to a container
1260 1260
 
1261 1261
 `POST /containers/(id or name)/attach`
1262 1262
 
... ...
@@ -1345,7 +1343,7 @@ The simplest way to implement the Attach protocol is the following:
1345 1345
     4.  Read the extracted size and output it on the correct output.
1346 1346
     5.  Goto 1.
1347 1347
 
1348
-### Attach to a container (websocket)
1348
+#### Attach to a container (websocket)
1349 1349
 
1350 1350
 `GET /containers/(id or name)/attach/ws`
1351 1351
 
... ...
@@ -1385,7 +1383,7 @@ Implements websocket protocol handshake according to [RFC 6455](http://tools.iet
1385 1385
 -   **404** – no such container
1386 1386
 -   **500** – server error
1387 1387
 
1388
-### Wait a container
1388
+#### Wait a container
1389 1389
 
1390 1390
 `POST /containers/(id or name)/wait`
1391 1391
 
... ...
@@ -1408,7 +1406,7 @@ Block until container `id` stops, then returns the exit code
1408 1408
 -   **404** – no such container
1409 1409
 -   **500** – server error
1410 1410
 
1411
-### Remove a container
1411
+#### Remove a container
1412 1412
 
1413 1413
 `DELETE /containers/(id or name)`
1414 1414
 
... ...
@@ -1437,14 +1435,14 @@ Remove the container `id` from the filesystem
1437 1437
 -   **409** – conflict
1438 1438
 -   **500** – server error
1439 1439
 
1440
-### Retrieving information about files and folders in a container
1440
+#### Retrieving information about files and folders in a container
1441 1441
 
1442 1442
 `HEAD /containers/(id or name)/archive`
1443 1443
 
1444 1444
 See the description of the `X-Docker-Container-Path-Stat` header in the
1445 1445
 following section.
1446 1446
 
1447
-### Get an archive of a filesystem resource in a container
1447
+#### Get an archive of a filesystem resource in a container
1448 1448
 
1449 1449
 `GET /containers/(id or name)/archive`
1450 1450
 
... ...
@@ -1509,7 +1507,7 @@ desired.
1509 1509
     - no such file or directory (**path** does not exist)
1510 1510
 - **500** - server error
1511 1511
 
1512
-### Extract an archive of files or folders to a directory in a container
1512
+#### Extract an archive of files or folders to a directory in a container
1513 1513
 
1514 1514
 `PUT /containers/(id or name)/archive`
1515 1515
 
... ...
@@ -1557,9 +1555,9 @@ Upload a tar archive to be extracted to a path in the filesystem of container
1557 1557
     - no such file or directory (**path** resource does not exist)
1558 1558
 - **500** – server error
1559 1559
 
1560
-## 3.2 Images
1560
+### 3.2 Images
1561 1561
 
1562
-### List Images
1562
+#### List Images
1563 1563
 
1564 1564
 `GET /images/json`
1565 1565
 
... ...
@@ -1652,7 +1650,7 @@ references on the command line.
1652 1652
   -   `since`=(`<image-name>[:<tag>]`,  `<image id>` or `<image@digest>`)
1653 1653
 -   **filter** - only return images with the specified name
1654 1654
 
1655
-### Build image from a Dockerfile
1655
+#### Build image from a Dockerfile
1656 1656
 
1657 1657
 `POST /build`
1658 1658
 
... ...
@@ -1758,7 +1756,7 @@ or being killed.
1758 1758
 -   **200** – no error
1759 1759
 -   **500** – server error
1760 1760
 
1761
-### Create an image
1761
+#### Create an image
1762 1762
 
1763 1763
 `POST /images/create`
1764 1764
 
... ...
@@ -1824,7 +1822,7 @@ a base64-encoded AuthConfig object.
1824 1824
 
1825 1825
 
1826 1826
 
1827
-### Inspect an image
1827
+#### Inspect an image
1828 1828
 
1829 1829
 `GET /images/(name)/json`
1830 1830
 
... ...
@@ -1942,7 +1940,7 @@ Return low-level information on the image `name`
1942 1942
 -   **404** – no such image
1943 1943
 -   **500** – server error
1944 1944
 
1945
-### Get the history of an image
1945
+#### Get the history of an image
1946 1946
 
1947 1947
 `GET /images/(name)/history`
1948 1948
 
... ...
@@ -1996,7 +1994,7 @@ Return the history of the image `name`
1996 1996
 -   **404** – no such image
1997 1997
 -   **500** – server error
1998 1998
 
1999
-### Push an image on the registry
1999
+#### Push an image on the registry
2000 2000
 
2001 2001
 `POST /images/(name)/push`
2002 2002
 
... ...
@@ -2058,7 +2056,7 @@ The push is cancelled if the HTTP connection is closed.
2058 2058
 -   **404** – no such image
2059 2059
 -   **500** – server error
2060 2060
 
2061
-### Tag an image into a repository
2061
+#### Tag an image into a repository
2062 2062
 
2063 2063
 `POST /images/(name)/tag`
2064 2064
 
... ...
@@ -2085,7 +2083,7 @@ Tag the image `name` into a repository
2085 2085
 -   **409** – conflict
2086 2086
 -   **500** – server error
2087 2087
 
2088
-### Remove an image
2088
+#### Remove an image
2089 2089
 
2090 2090
 `DELETE /images/(name)`
2091 2091
 
... ...
@@ -2118,7 +2116,7 @@ Remove the image `name` from the filesystem
2118 2118
 -   **409** – conflict
2119 2119
 -   **500** – server error
2120 2120
 
2121
-### Search images
2121
+#### Search images
2122 2122
 
2123 2123
 `GET /images/search`
2124 2124
 
... ...
@@ -2176,9 +2174,9 @@ Search for an image on [Docker Hub](https://hub.docker.com).
2176 2176
 -   **200** – no error
2177 2177
 -   **500** – server error
2178 2178
 
2179
-## 3.3 Misc
2179
+### 3.3 Misc
2180 2180
 
2181
-### Check auth configuration
2181
+#### Check auth configuration
2182 2182
 
2183 2183
 `POST /auth`
2184 2184
 
... ...
@@ -2211,7 +2209,7 @@ if available, for accessing the registry without password.
2211 2211
 -   **204** – no error
2212 2212
 -   **500** – server error
2213 2213
 
2214
-### Display system-wide information
2214
+#### Display system-wide information
2215 2215
 
2216 2216
 `GET /info`
2217 2217
 
... ...
@@ -2304,7 +2302,7 @@ Display system-wide information
2304 2304
 -   **200** – no error
2305 2305
 -   **500** – server error
2306 2306
 
2307
-### Show the docker version information
2307
+#### Show the docker version information
2308 2308
 
2309 2309
 `GET /version`
2310 2310
 
... ...
@@ -2336,7 +2334,7 @@ Show the docker version information
2336 2336
 -   **200** – no error
2337 2337
 -   **500** – server error
2338 2338
 
2339
-### Ping the docker server
2339
+#### Ping the docker server
2340 2340
 
2341 2341
 `GET /_ping`
2342 2342
 
... ...
@@ -2358,7 +2356,7 @@ Ping the docker server
2358 2358
 -   **200** - no error
2359 2359
 -   **500** - server error
2360 2360
 
2361
-### Create a new image from a container's changes
2361
+#### Create a new image from a container's changes
2362 2362
 
2363 2363
 `POST /commit`
2364 2364
 
... ...
@@ -2430,7 +2428,7 @@ Create a new image from a container's changes
2430 2430
 -   **404** – no such container
2431 2431
 -   **500** – server error
2432 2432
 
2433
-### Monitor Docker's events
2433
+#### Monitor Docker's events
2434 2434
 
2435 2435
 `GET /events`
2436 2436
 
... ...
@@ -2635,7 +2633,7 @@ Docker daemon report the following event:
2635 2635
 -   **200** – no error
2636 2636
 -   **500** – server error
2637 2637
 
2638
-### Get a tarball containing all images in a repository
2638
+#### Get a tarball containing all images in a repository
2639 2639
 
2640 2640
 `GET /images/(name)/get`
2641 2641
 
... ...
@@ -2665,7 +2663,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2665 2665
 -   **200** – no error
2666 2666
 -   **500** – server error
2667 2667
 
2668
-### Get a tarball containing all images
2668
+#### Get a tarball containing all images
2669 2669
 
2670 2670
 `GET /images/get`
2671 2671
 
... ...
@@ -2694,7 +2692,7 @@ See the [image tarball format](#image-tarball-format) for more details.
2694 2694
 -   **200** – no error
2695 2695
 -   **500** – server error
2696 2696
 
2697
-### Load a tarball with a set of images and tags into docker
2697
+#### Load a tarball with a set of images and tags into docker
2698 2698
 
2699 2699
 `POST /images/load`
2700 2700
 
... ...
@@ -2743,7 +2741,7 @@ action completes.
2743 2743
 -   **200** – no error
2744 2744
 -   **500** – server error
2745 2745
 
2746
-### Image tarball format
2746
+#### Image tarball format
2747 2747
 
2748 2748
 An image tarball contains one directory per image layer (named using its long ID),
2749 2749
 each containing these files:
... ...
@@ -2764,7 +2762,7 @@ the root that contains a list of repository and tag names mapped to layer IDs.
2764 2764
 }
2765 2765
 ```
2766 2766
 
2767
-### Exec Create
2767
+#### Exec Create
2768 2768
 
2769 2769
 `POST /containers/(id or name)/exec`
2770 2770
 
... ...
@@ -2818,7 +2816,7 @@ Sets up an exec instance in a running container `id`
2818 2818
 -   **409** - container is paused
2819 2819
 -   **500** - server error
2820 2820
 
2821
-### Exec Start
2821
+#### Exec Start
2822 2822
 
2823 2823
 `POST /exec/(id)/start`
2824 2824
 
... ...
@@ -2860,7 +2858,7 @@ interactive session with the `exec` command.
2860 2860
 
2861 2861
 Similar to the stream behavior of `POST /containers/(id or name)/attach` API
2862 2862
 
2863
-### Exec Resize
2863
+#### Exec Resize
2864 2864
 
2865 2865
 `POST /exec/(id)/resize`
2866 2866
 
... ...
@@ -2887,7 +2885,7 @@ This API is valid only if `tty` was specified as part of creating and starting t
2887 2887
 -   **201** – no error
2888 2888
 -   **404** – no such exec instance
2889 2889
 
2890
-### Exec Inspect
2890
+#### Exec Inspect
2891 2891
 
2892 2892
 `GET /exec/(id)/json`
2893 2893
 
... ...
@@ -2930,9 +2928,9 @@ Return low-level information about the `exec` command `id`.
2930 2930
 -   **404** – no such exec instance
2931 2931
 -   **500** - server error
2932 2932
 
2933
-## 3.4 Volumes
2933
+### 3.4 Volumes
2934 2934
 
2935
-### List volumes
2935
+#### List volumes
2936 2936
 
2937 2937
 `GET /volumes`
2938 2938
 
... ...
@@ -2970,7 +2968,7 @@ Return low-level information about the `exec` command `id`.
2970 2970
 -   **200** - no error
2971 2971
 -   **500** - server error
2972 2972
 
2973
-### Create a volume
2973
+#### Create a volume
2974 2974
 
2975 2975
 `POST /volumes/create`
2976 2976
 
... ...
@@ -3027,7 +3025,7 @@ Create a volume
3027 3027
 Refer to the [inspect a volume](#inspect-a-volume) section or details about the
3028 3028
 JSON fields returned in the response.
3029 3029
 
3030
-### Inspect a volume
3030
+#### Inspect a volume
3031 3031
 
3032 3032
 `GET /volumes/(name)`
3033 3033
 
... ...
@@ -3079,7 +3077,7 @@ response.
3079 3079
 - **Scope** - Scope describes the level at which the volume exists, can be one of
3080 3080
     `global` for cluster-wide or `local` for machine level. The default is `local`.
3081 3081
 
3082
-### Remove a volume
3082
+#### Remove a volume
3083 3083
 
3084 3084
 `DELETE /volumes/(name)`
3085 3085
 
... ...
@@ -3100,9 +3098,9 @@ Instruct the driver to remove the volume (`name`).
3100 3100
 -   **409** - volume is in use and cannot be removed
3101 3101
 -   **500** - server error
3102 3102
 
3103
-## 3.5 Networks
3103
+### 3.5 Networks
3104 3104
 
3105
-### List networks
3105
+#### List networks
3106 3106
 
3107 3107
 `GET /networks`
3108 3108
 
... ...
@@ -3194,7 +3192,7 @@ Content-Type: application/json
3194 3194
 -   **200** - no error
3195 3195
 -   **500** - server error
3196 3196
 
3197
-### Inspect network
3197
+#### Inspect network
3198 3198
 
3199 3199
 `GET /networks/<network-id>`
3200 3200
 
... ...
@@ -3256,7 +3254,7 @@ Content-Type: application/json
3256 3256
 -   **200** - no error
3257 3257
 -   **404** - network not found
3258 3258
 
3259
-### Create a network
3259
+#### Create a network
3260 3260
 
3261 3261
 `POST /networks/create`
3262 3262
 
... ...
@@ -3339,7 +3337,7 @@ Content-Type: application/json
3339 3339
 - **Options** - Network specific options to be used by the drivers
3340 3340
 - **Labels** - Labels to set on the network, specified as a map: `{"key":"value" [,"key2":"value2"]}`
3341 3341
 
3342
-### Connect a container to a network
3342
+#### Connect a container to a network
3343 3343
 
3344 3344
 `POST /networks/(id)/connect`
3345 3345
 
... ...
@@ -3377,7 +3375,7 @@ Content-Type: application/json
3377 3377
 
3378 3378
 - **container** - container-id/name to be connected to the network
3379 3379
 
3380
-### Disconnect a container from a network
3380
+#### Disconnect a container from a network
3381 3381
 
3382 3382
 `POST /networks/(id)/disconnect`
3383 3383
 
... ...
@@ -3411,7 +3409,7 @@ Content-Type: application/json
3411 3411
 - **Container** - container-id/name to be disconnected from a network
3412 3412
 - **Force** - Force the container to disconnect from a network
3413 3413
 
3414
-### Remove a network
3414
+#### Remove a network
3415 3415
 
3416 3416
 `DELETE /networks/(id)`
3417 3417
 
... ...
@@ -3431,9 +3429,9 @@ Instruct the driver to remove the network (`id`).
3431 3431
 -   **404** - no such network
3432 3432
 -   **500** - server error
3433 3433
 
3434
-## 3.6 Plugins (experimental)
3434
+### 3.6 Plugins (experimental)
3435 3435
 
3436
-### List plugins
3436
+#### List plugins
3437 3437
 
3438 3438
 `GET /plugins`
3439 3439
 
... ...
@@ -3563,7 +3561,7 @@ Content-Type: application/json
3563 3563
 -   **200** - no error
3564 3564
 -   **500** - server error
3565 3565
 
3566
-### Install a plugin
3566
+#### Install a plugin
3567 3567
 
3568 3568
 `POST /plugins/pull?name=<plugin name>`
3569 3569
 
... ...
@@ -3625,7 +3623,7 @@ Content-Length: 175
3625 3625
       name must have at least one component
3626 3626
 -   **500** - plugin already exists
3627 3627
 
3628
-### Inspect a plugin
3628
+#### Inspect a plugin
3629 3629
 
3630 3630
 `GET /plugins/(plugin name)`
3631 3631
 
... ...
@@ -3758,7 +3756,7 @@ Content-Type: application/json
3758 3758
 -   **200** - no error
3759 3759
 -   **404** - plugin not installed
3760 3760
 
3761
-### Enable a plugin
3761
+#### Enable a plugin
3762 3762
 
3763 3763
 `POST /plugins/(plugin name)/enable`
3764 3764
 
... ...
@@ -3786,7 +3784,7 @@ Content-Type: text/plain; charset=utf-8
3786 3786
 -   **200** - no error
3787 3787
 -   **500** - plugin is already enabled
3788 3788
 
3789
-### Disable a plugin
3789
+#### Disable a plugin
3790 3790
 
3791 3791
 `POST /plugins/(plugin name)/disable`
3792 3792
 
... ...
@@ -3814,7 +3812,7 @@ Content-Type: text/plain; charset=utf-8
3814 3814
 -   **200** - no error
3815 3815
 -   **500** - plugin is already disabled
3816 3816
 
3817
-### Remove a plugin
3817
+#### Remove a plugin
3818 3818
 
3819 3819
 `DELETE /plugins/(plugin name)`
3820 3820
 
... ...
@@ -3844,7 +3842,7 @@ Content-Type: text/plain; charset=utf-8
3844 3844
 
3845 3845
 <!-- TODO Document "docker plugin push" endpoint once we have "plugin build"
3846 3846
 
3847
-### Push a plugin
3847
+#### Push a plugin
3848 3848
 
3849 3849
 `POST /v1.24/plugins/tiborvass/(plugin name)/push HTTP/1.1`
3850 3850
 
... ...
@@ -3870,11 +3868,11 @@ an image](#create-an-image) section for more details.
3870 3870
 
3871 3871
 -->
3872 3872
 
3873
-## 3.7 Nodes
3873
+### 3.7 Nodes
3874 3874
 
3875 3875
 **Note**: Node operations require the engine to be part of a swarm.
3876 3876
 
3877
-### List nodes
3877
+#### List nodes
3878 3878
 
3879 3879
 
3880 3880
 `GET /nodes`
... ...
@@ -3967,7 +3965,7 @@ List nodes
3967 3967
 - **200** – no error
3968 3968
 - **500** – server error
3969 3969
 
3970
-### Inspect a node
3970
+#### Inspect a node
3971 3971
 
3972 3972
 
3973 3973
 `GET /nodes/(id or name)`
... ...
@@ -4049,7 +4047,7 @@ Return low-level information on the node `id`
4049 4049
 -   **404** – no such node
4050 4050
 -   **500** – server error
4051 4051
 
4052
-### Remove a node
4052
+#### Remove a node
4053 4053
 
4054 4054
 
4055 4055
 `DELETE /nodes/(id or name)`
... ...
@@ -4077,7 +4075,7 @@ Remove a node from the swarm.
4077 4077
 -   **404** – no such node
4078 4078
 -   **500** – server error
4079 4079
 
4080
-### Update a node
4080
+#### Update a node
4081 4081
 
4082 4082
 
4083 4083
 `POST /nodes/(id)/update`
... ...
@@ -4132,9 +4130,9 @@ JSON Parameters:
4132 4132
 -   **404** – no such node
4133 4133
 -   **500** – server error
4134 4134
 
4135
-## 3.8 Swarm
4135
+### 3.8 Swarm
4136 4136
 
4137
-### Inspect swarm
4137
+#### Inspect swarm
4138 4138
 
4139 4139
 
4140 4140
 `GET /swarm`
... ...
@@ -4182,7 +4180,7 @@ Inspect swarm
4182 4182
 
4183 4183
 - **200** - no error
4184 4184
 
4185
-### Initialize a new swarm
4185
+#### Initialize a new swarm
4186 4186
 
4187 4187
 
4188 4188
 `POST /swarm/init`
... ...
@@ -4258,7 +4256,7 @@ JSON Parameters:
4258 4258
             - **Options** - An object with key/value pairs that are interpreted
4259 4259
               as protocol-specific options for the external CA driver.
4260 4260
 
4261
-### Join an existing swarm
4261
+#### Join an existing swarm
4262 4262
 
4263 4263
 `POST /swarm/join`
4264 4264
 
... ...
@@ -4300,7 +4298,7 @@ JSON Parameters:
4300 4300
 - **RemoteAddr** – Address of any manager node already participating in the swarm.
4301 4301
 - **JoinToken** – Secret token for joining this swarm.
4302 4302
 
4303
-### Leave a swarm
4303
+#### Leave a swarm
4304 4304
 
4305 4305
 
4306 4306
 `POST /swarm/leave`
... ...
@@ -4326,7 +4324,7 @@ Leave a swarm
4326 4326
 - **200** – no error
4327 4327
 - **406** – node is not part of a swarm
4328 4328
 
4329
-### Update a swarm
4329
+#### Update a swarm
4330 4330
 
4331 4331
 
4332 4332
 `POST /swarm/update`
... ...
@@ -4407,11 +4405,11 @@ JSON Parameters:
4407 4407
     - **Worker** - Token to use for joining as a worker.
4408 4408
     - **Manager** - Token to use for joining as a manager.
4409 4409
 
4410
-## 3.9 Services
4410
+### 3.9 Services
4411 4411
 
4412 4412
 **Note**: Service operations require to first be part of a swarm.
4413 4413
 
4414
-### List services
4414
+#### List services
4415 4415
 
4416 4416
 
4417 4417
 `GET /services`
... ...
@@ -4516,7 +4514,7 @@ List services
4516 4516
 - **200** – no error
4517 4517
 - **500** – server error
4518 4518
 
4519
-### Create a service
4519
+#### Create a service
4520 4520
 
4521 4521
 `POST /services/create`
4522 4522
 
... ...
@@ -4689,7 +4687,7 @@ image](#create-an-image) section for more details.
4689 4689
   section for more details.
4690 4690
 
4691 4691
 
4692
-### Remove a service
4692
+#### Remove a service
4693 4693
 
4694 4694
 
4695 4695
 `DELETE /services/(id or name)`
... ...
@@ -4712,7 +4710,7 @@ Stop and remove the service `id`
4712 4712
 -   **404** – no such service
4713 4713
 -   **500** – server error
4714 4714
 
4715
-### Inspect one or more services
4715
+#### Inspect one or more services
4716 4716
 
4717 4717
 
4718 4718
 `GET /services/(id or name)`
... ...
@@ -4801,7 +4799,7 @@ Return information on the service `id`.
4801 4801
 -   **404** – no such service
4802 4802
 -   **500** – server error
4803 4803
 
4804
-### Update a service
4804
+#### Update a service
4805 4805
 
4806 4806
 `POST /services/(id or name)/update`
4807 4807
 
... ...
@@ -4934,11 +4932,11 @@ image](#create-an-image) section for more details.
4934 4934
 -   **404** – no such service
4935 4935
 -   **500** – server error
4936 4936
 
4937
-## 3.10 Tasks
4937
+### 3.10 Tasks
4938 4938
 
4939 4939
 **Note**: Task operations require the engine to be part of a swarm.
4940 4940
 
4941
-### List tasks
4941
+#### List tasks
4942 4942
 
4943 4943
 
4944 4944
 `GET /tasks`
... ...
@@ -5136,7 +5134,7 @@ List tasks
5136 5136
 - **200** – no error
5137 5137
 - **500** – server error
5138 5138
 
5139
-### Inspect a task
5139
+#### Inspect a task
5140 5140
 
5141 5141
 
5142 5142
 `GET /tasks/(task id)`
... ...
@@ -5239,9 +5237,9 @@ Get details on a task
5239 5239
 - **404** – unknown task
5240 5240
 - **500** – server error
5241 5241
 
5242
-# 4. Going further
5242
+## 4. Going further
5243 5243
 
5244
-## 4.1 Inside `docker run`
5244
+### 4.1 Inside `docker run`
5245 5245
 
5246 5246
 As an example, the `docker run` command line makes the following API calls:
5247 5247
 
... ...
@@ -5259,7 +5257,7 @@ As an example, the `docker run` command line makes the following API calls:
5259 5259
 
5260 5260
 - If in detached mode or only `stdin` is attached, display the container's id.
5261 5261
 
5262
-## 4.2 Hijacking
5262
+### 4.2 Hijacking
5263 5263
 
5264 5264
 In this version of the API, `/attach`, uses hijacking to transport `stdin`,
5265 5265
 `stdout`, and `stderr` on the same socket.
... ...
@@ -5274,7 +5272,7 @@ When Docker daemon detects the `Upgrade` header, it switches its status code
5274 5274
 from **200 OK** to **101 UPGRADED** and resends the same headers.
5275 5275
 
5276 5276
 
5277
-## 4.3 CORS Requests
5277
+### 4.3 CORS Requests
5278 5278
 
5279 5279
 To set cross origin requests to the Engine API please give values to
5280 5280
 `--api-cors-header` when running Docker in daemon mode. Set * (asterisk) allows all,
... ...
@@ -72,7 +72,11 @@ keywords: "API, Docker, rcli, REST, documentation"
72 72
 * `DELETE /plugins/(plugin name)` delete a plugin.
73 73
 * `POST /node/(id or name)/update` now accepts both `id` or `name` to identify the node to update.
74 74
 * `GET /images/json` now support a `reference` filter.
75
-
75
+* `GET /secrets` returns information on the secrets.
76
+* `POST /secrets/create` creates a secret.
77
+* `DELETE /secrets/{id}` removes the secret `id`.
78
+* `GET /secrets/{id}` returns information on the secret `id`.
79
+* `POST /secrets/{id}/update` updates the secret `id`.
76 80
 
77 81
 ## v1.24 API changes
78 82
 
... ...
@@ -32,7 +32,7 @@ The `filter` param to filter the list of image by reference (name or name:tag) i
32 32
 
33 33
 **Target For Removal In Release: v1.16**
34 34
 
35
-`repository:shortid` syntax for referencing images is very little used, collides with with tag references can be confused with digest references.
35
+`repository:shortid` syntax for referencing images is very little used, collides with tag references can be confused with digest references.
36 36
 
37 37
 ### `docker daemon` subcommand
38 38
 **Deprecated In Release: [v1.13](https://github.com/docker/docker/releases/tag/v1.13.0)**
... ...
@@ -82,7 +82,6 @@ Options:
82 82
       --memory-reservation string   Memory soft limit
83 83
       --memory-swap string          Swap limit equal to memory plus swap: '-1' to enable unlimited swap
84 84
       --memory-swappiness int       Tune container memory swappiness (0 to 100) (default -1)
85
-      --mount value                 Attach a filesytem mount to the container (default [])
86 85
       --name string                 Assign a name to the container
87 86
       --network-alias value         Add network-scoped alias for the container (default [])
88 87
       --network string              Connect a container to a network (default "default")
... ...
@@ -22,7 +22,7 @@ Usage:  docker node ps [OPTIONS] [NODE...]
22 22
 List tasks running on one or more nodes, defaults to current node.
23 23
 
24 24
 Options:
25
-  -a, --all            Show all tasks (default shows tasks that are or will be running)
25
+  -a, --all            Display all instances
26 26
   -f, --filter value   Filter output based on conditions provided
27 27
       --help           Print usage
28 28
       --no-resolve     Do not map IDs to Names
... ...
@@ -16,13 +16,13 @@ keywords: "plugin, inspect"
16 16
 # plugin inspect
17 17
 
18 18
 ```markdown
19
-Usage:  docker plugin inspect [OPTIONS] PLUGIN [PLUGIN...]
19
+Usage:	docker plugin inspect [OPTIONS] PLUGIN|ID [PLUGIN|ID...]
20 20
 
21 21
 Display detailed information on one or more plugins
22 22
 
23 23
 Options:
24
-      -f, --format string   Format the output using the given Go template
25
-          --help            Print usage
24
+  -f, --format string   Format the output using the given Go template
25
+      --help            Print usage
26 26
 ```
27 27
 
28 28
 Returns information about a plugin. By default, this command renders all results
... ...
@@ -92,7 +92,6 @@ Options:
92 92
       --memory-reservation string   Memory soft limit
93 93
       --memory-swap string          Swap limit equal to memory plus swap: '-1' to enable unlimited swap
94 94
       --memory-swappiness int       Tune container memory swappiness (0 to 100) (default -1)
95
-      --mount value                 Attach a filesystem mount to the container (default [])
96 95
       --name string                 Assign a name to the container
97 96
       --network-alias value         Add network-scoped alias for the container (default [])
98 97
       --network string              Connect a container to a network
... ...
@@ -284,21 +283,6 @@ of a bind mount must be a local directory, not a file.
284 284
 
285 285
 For in-depth information about volumes, refer to [manage data in containers](https://docs.docker.com/engine/tutorials/dockervolumes/)
286 286
 
287
-### Add bin-mounts or volumes using the --mount flag
288
-
289
-The `--mount` flag allows you to mount volumes, host-directories and `tmpfs`
290
-mounts in a container.
291
-
292
-The `--mount` flag supports most options that are supported by the `-v` or the
293
-`--volume` flag, but uses a different syntax. For in-depth information on the
294
-`--mount` flag, and a comparison between `--volume` and `--mount`, refer to
295
-the [service create command reference](service_create.md#add-bind-mounts-or-volumes).
296
-
297
-Examples:
298
-
299
-    $ docker run --read-only --mount type=volume,target=/icanwrite busybox touch /icanwrite/here
300
-    $ docker run -t -i --mount type=bind,src=/data,dst=/data busybox sh
301
-
302 287
 ### Publish or expose port (-p, --expose)
303 288
 
304 289
     $ docker run -p 127.0.0.1:80:8080 ubuntu bash
... ...
@@ -678,7 +662,7 @@ The `credentialspec` must be in the format `file://spec.txt` or `registry://keyn
678 678
 
679 679
 ### Stop container with timeout (--stop-timeout)
680 680
 
681
-The `--stop-timeout` flag sets the the timeout (in seconds) that a pre-defined (see `--stop-signal`) system call
681
+The `--stop-timeout` flag sets the timeout (in seconds) that a pre-defined (see `--stop-signal`) system call
682 682
 signal that will be sent to the container to exit. After timeout elapses the container will be killed with SIGKILL.
683 683
 
684 684
 ### Specify isolation technology for container (--isolation)
... ...
@@ -27,9 +27,9 @@ Options:
27 27
   -q, --quiet          Only display IDs
28 28
 ```
29 29
 
30
-Run this command from a manager to list the secrets in the Swarm.
30
+Run this command on a manager node to list the secrets in the Swarm.
31 31
 
32
-On a manager node:
32
+## Examples
33 33
 
34 34
 ```bash
35 35
 $ docker secret ls
... ...
@@ -22,7 +22,6 @@ Usage:  docker service ps [OPTIONS] SERVICE
22 22
 List the tasks of a service
23 23
 
24 24
 Options:
25
-  -a, --all             Show all tasks (default shows tasks that are or will be running)
26 25
   -f, --filter filter   Filter output based on conditions provided
27 26
       --help            Print usage
28 27
       --no-resolve      Do not map IDs to Names
... ...
@@ -280,7 +280,7 @@ A virtual machine is a program that emulates a complete computer and imitates de
280 280
 It shares physical hardware resources with other users but isolates the operating system. The
281 281
 end user has the same experience on a Virtual Machine as they would have on dedicated hardware.
282 282
 
283
-Compared to to containers, a virtual machine is heavier to run, provides more isolation,
283
+Compared to containers, a virtual machine is heavier to run, provides more isolation,
284 284
 gets its own set of resources and does minimal sharing.
285 285
 
286 286
 *Also known as : VM*
... ...
@@ -4,7 +4,7 @@ export SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4 4
 source "${SCRIPTDIR}/.validate"
5 5
 
6 6
 IFS=$'\n'
7
-files=( $(validate_diff --diff-filter=ACMR --name-only -- 'api/types/' || true) )
7
+files=( $(validate_diff --diff-filter=ACMR --name-only -- 'api/types/' 'api/swagger.yaml' || true) )
8 8
 unset IFS
9 9
 
10 10
 if [ ${#files[@]} -gt 0 ]; then
... ...
@@ -26,5 +26,5 @@ if [ ${#files[@]} -gt 0 ]; then
26 26
 		echo 'Congratulations! All api changes are done the right way.'
27 27
 	fi
28 28
 else
29
-    echo 'No api/types/ changes in diff.'
29
+    echo 'No api/types/ or api/swagger.yaml changes in diff.'
30 30
 fi
... ...
@@ -234,7 +234,9 @@ func (s *saveSession) saveImage(id image.ID) (map[layer.DiffID]distribution.Desc
234 234
 	var layers []string
235 235
 	var foreignSrcs map[layer.DiffID]distribution.Descriptor
236 236
 	for i := range img.RootFS.DiffIDs {
237
-		v1Img := image.V1Image{}
237
+		v1Img := image.V1Image{
238
+			Created: img.Created,
239
+		}
238 240
 		if i == len(img.RootFS.DiffIDs)-1 {
239 241
 			v1Img = img.V1Image
240 242
 		}
... ...
@@ -317,7 +317,7 @@ func (d *SwarmDaemon) getSecret(c *check.C, id string) *swarm.Secret {
317 317
 func (d *SwarmDaemon) deleteSecret(c *check.C, id string) {
318 318
 	status, out, err := d.SockRequest("DELETE", "/secrets/"+id, nil)
319 319
 	c.Assert(err, checker.IsNil, check.Commentf(string(out)))
320
-	c.Assert(status, checker.Equals, http.StatusOK, check.Commentf("output: %q", string(out)))
320
+	c.Assert(status, checker.Equals, http.StatusNoContent, check.Commentf("output: %q", string(out)))
321 321
 }
322 322
 
323 323
 func (d *SwarmDaemon) getSwarm(c *check.C) swarm.Swarm {
... ...
@@ -728,7 +728,7 @@ func (s *DockerSuite) TestContainerAPIInvalidPortSyntax(c *check.C) {
728 728
 	c.Assert(string(b[:]), checker.Contains, "invalid port")
729 729
 }
730 730
 
731
-func (s *DockerSuite) TestContainerAPIInvalidRestartPolicyName(c *check.C) {
731
+func (s *DockerSuite) TestContainerAPIRestartPolicyInvalidPolicyName(c *check.C) {
732 732
 	config := `{
733 733
 		"Image": "busybox",
734 734
 		"HostConfig": {
... ...
@@ -748,7 +748,7 @@ func (s *DockerSuite) TestContainerAPIInvalidRestartPolicyName(c *check.C) {
748 748
 	c.Assert(string(b[:]), checker.Contains, "invalid restart policy")
749 749
 }
750 750
 
751
-func (s *DockerSuite) TestContainerAPIInvalidRestartPolicyRetryMismatch(c *check.C) {
751
+func (s *DockerSuite) TestContainerAPIRestartPolicyRetryMismatch(c *check.C) {
752 752
 	config := `{
753 753
 		"Image": "busybox",
754 754
 		"HostConfig": {
... ...
@@ -765,10 +765,10 @@ func (s *DockerSuite) TestContainerAPIInvalidRestartPolicyRetryMismatch(c *check
765 765
 
766 766
 	b, err := readBody(body)
767 767
 	c.Assert(err, checker.IsNil)
768
-	c.Assert(string(b[:]), checker.Contains, "maximum restart count not valid with restart policy")
768
+	c.Assert(string(b[:]), checker.Contains, "maximum retry count cannot be used with restart policy")
769 769
 }
770 770
 
771
-func (s *DockerSuite) TestContainerAPIInvalidRestartPolicyPositiveRetryCount(c *check.C) {
771
+func (s *DockerSuite) TestContainerAPIRestartPolicyNegativeRetryCount(c *check.C) {
772 772
 	config := `{
773 773
 		"Image": "busybox",
774 774
 		"HostConfig": {
... ...
@@ -785,7 +785,23 @@ func (s *DockerSuite) TestContainerAPIInvalidRestartPolicyPositiveRetryCount(c *
785 785
 
786 786
 	b, err := readBody(body)
787 787
 	c.Assert(err, checker.IsNil)
788
-	c.Assert(string(b[:]), checker.Contains, "maximum restart count must be a positive integer")
788
+	c.Assert(string(b[:]), checker.Contains, "maximum retry count cannot be negative")
789
+}
790
+
791
+func (s *DockerSuite) TestContainerAPIRestartPolicyDefaultRetryCount(c *check.C) {
792
+	config := `{
793
+		"Image": "busybox",
794
+		"HostConfig": {
795
+			"RestartPolicy": {
796
+				"Name": "on-failure",
797
+				"MaximumRetryCount": 0
798
+			}
799
+		}
800
+	}`
801
+
802
+	res, _, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json")
803
+	c.Assert(err, checker.IsNil)
804
+	c.Assert(res.StatusCode, checker.Equals, http.StatusCreated)
789 805
 }
790 806
 
791 807
 // Issue 7941 - test to make sure a "null" in JSON is just ignored.
... ...
@@ -7311,3 +7311,19 @@ RUN ["cat", "/foo/file"]
7311 7311
 		c.Fatal(err)
7312 7312
 	}
7313 7313
 }
7314
+
7315
+// Case-insensitive environment variables on Windows
7316
+func (s *DockerSuite) TestBuildWindowsEnvCaseInsensitive(c *check.C) {
7317
+	testRequires(c, DaemonIsWindows)
7318
+	name := "testbuildwindowsenvcaseinsensitive"
7319
+	if _, err := buildImage(name, `
7320
+		FROM `+WindowsBaseImage+`
7321
+		ENV FOO=bar foo=bar
7322
+  `, true); err != nil {
7323
+		c.Fatal(err)
7324
+	}
7325
+	res := inspectFieldJSON(c, name, "Config.Env")
7326
+	if res != `["foo=bar"]` { // Should not have FOO=bar in it - takes the last one processed. And only one entry as deduped.
7327
+		c.Fatalf("Case insensitive environment variables on Windows failed. Got %s", res)
7328
+	}
7329
+}
... ...
@@ -201,3 +201,52 @@ func (s *DockerSuite) TestPluginCreate(c *check.C) {
201 201
 	// The output will consists of one HEADER line and one line of foo/bar-driver
202 202
 	c.Assert(len(strings.Split(strings.TrimSpace(out), "\n")), checker.Equals, 2)
203 203
 }
204
+
205
+func (s *DockerSuite) TestPluginInspect(c *check.C) {
206
+	testRequires(c, DaemonIsLinux, Network)
207
+	_, _, err := dockerCmdWithError("plugin", "install", "--grant-all-permissions", pNameWithTag)
208
+	c.Assert(err, checker.IsNil)
209
+
210
+	out, _, err := dockerCmdWithError("plugin", "ls")
211
+	c.Assert(err, checker.IsNil)
212
+	c.Assert(out, checker.Contains, pName)
213
+	c.Assert(out, checker.Contains, pTag)
214
+	c.Assert(out, checker.Contains, "true")
215
+
216
+	// Find the ID first
217
+	out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", pNameWithTag)
218
+	c.Assert(err, checker.IsNil)
219
+	id := strings.TrimSpace(out)
220
+	c.Assert(id, checker.Not(checker.Equals), "")
221
+
222
+	// Long form
223
+	out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", id)
224
+	c.Assert(err, checker.IsNil)
225
+	c.Assert(strings.TrimSpace(out), checker.Equals, id)
226
+
227
+	// Short form
228
+	out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", id[:5])
229
+	c.Assert(err, checker.IsNil)
230
+	c.Assert(strings.TrimSpace(out), checker.Equals, id)
231
+
232
+	// Name with tag form
233
+	out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", pNameWithTag)
234
+	c.Assert(err, checker.IsNil)
235
+	c.Assert(strings.TrimSpace(out), checker.Equals, id)
236
+
237
+	// Name without tag form
238
+	out, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", pName)
239
+	c.Assert(err, checker.IsNil)
240
+	c.Assert(strings.TrimSpace(out), checker.Equals, id)
241
+
242
+	_, _, err = dockerCmdWithError("plugin", "disable", pNameWithTag)
243
+	c.Assert(err, checker.IsNil)
244
+
245
+	out, _, err = dockerCmdWithError("plugin", "remove", pNameWithTag)
246
+	c.Assert(err, checker.IsNil)
247
+	c.Assert(out, checker.Contains, pNameWithTag)
248
+
249
+	// After remove nothing should be found
250
+	_, _, err = dockerCmdWithError("plugin", "inspect", "-f", "{{.Id}}", id[:5])
251
+	c.Assert(err, checker.NotNil)
252
+}
... ...
@@ -74,7 +74,7 @@ func (s *DockerSuite) TestRestartWithVolumes(c *check.C) {
74 74
 }
75 75
 
76 76
 func (s *DockerSuite) TestRestartPolicyNO(c *check.C) {
77
-	out, _ := dockerCmd(c, "run", "-d", "--restart=no", "busybox", "false")
77
+	out, _ := dockerCmd(c, "create", "--restart=no", "busybox")
78 78
 
79 79
 	id := strings.TrimSpace(string(out))
80 80
 	name := inspectField(c, id, "HostConfig.RestartPolicy.Name")
... ...
@@ -82,7 +82,7 @@ func (s *DockerSuite) TestRestartPolicyNO(c *check.C) {
82 82
 }
83 83
 
84 84
 func (s *DockerSuite) TestRestartPolicyAlways(c *check.C) {
85
-	out, _ := dockerCmd(c, "run", "-d", "--restart=always", "busybox", "false")
85
+	out, _ := dockerCmd(c, "create", "--restart=always", "busybox")
86 86
 
87 87
 	id := strings.TrimSpace(string(out))
88 88
 	name := inspectField(c, id, "HostConfig.RestartPolicy.Name")
... ...
@@ -95,12 +95,36 @@ func (s *DockerSuite) TestRestartPolicyAlways(c *check.C) {
95 95
 }
96 96
 
97 97
 func (s *DockerSuite) TestRestartPolicyOnFailure(c *check.C) {
98
-	out, _ := dockerCmd(c, "run", "-d", "--restart=on-failure:1", "busybox", "false")
98
+	out, _, err := dockerCmdWithError("create", "--restart=on-failure:-1", "busybox")
99
+	c.Assert(err, check.NotNil, check.Commentf(out))
100
+	c.Assert(out, checker.Contains, "maximum retry count cannot be negative")
101
+
102
+	out, _ = dockerCmd(c, "create", "--restart=on-failure:1", "busybox")
99 103
 
100 104
 	id := strings.TrimSpace(string(out))
101 105
 	name := inspectField(c, id, "HostConfig.RestartPolicy.Name")
106
+	maxRetry := inspectField(c, id, "HostConfig.RestartPolicy.MaximumRetryCount")
107
+
102 108
 	c.Assert(name, checker.Equals, "on-failure")
109
+	c.Assert(maxRetry, checker.Equals, "1")
110
+
111
+	out, _ = dockerCmd(c, "create", "--restart=on-failure:0", "busybox")
103 112
 
113
+	id = strings.TrimSpace(string(out))
114
+	name = inspectField(c, id, "HostConfig.RestartPolicy.Name")
115
+	maxRetry = inspectField(c, id, "HostConfig.RestartPolicy.MaximumRetryCount")
116
+
117
+	c.Assert(name, checker.Equals, "on-failure")
118
+	c.Assert(maxRetry, checker.Equals, "0")
119
+
120
+	out, _ = dockerCmd(c, "create", "--restart=on-failure", "busybox")
121
+
122
+	id = strings.TrimSpace(string(out))
123
+	name = inspectField(c, id, "HostConfig.RestartPolicy.Name")
124
+	maxRetry = inspectField(c, id, "HostConfig.RestartPolicy.MaximumRetryCount")
125
+
126
+	c.Assert(name, checker.Equals, "on-failure")
127
+	c.Assert(maxRetry, checker.Equals, "0")
104 128
 }
105 129
 
106 130
 // a good container with --restart=on-failure:3
... ...
@@ -4590,184 +4590,6 @@ func (s *DockerSuite) TestRunDuplicateMount(c *check.C) {
4590 4590
 	c.Assert(out, checker.Contains, "null")
4591 4591
 }
4592 4592
 
4593
-func (s *DockerSuite) TestRunMount(c *check.C) {
4594
-	testRequires(c, DaemonIsLinux, SameHostDaemon, NotUserNamespace)
4595
-
4596
-	// mnt1, mnt2, and testCatFooBar are commonly used in multiple test cases
4597
-	tmpDir, err := ioutil.TempDir("", "mount")
4598
-	if err != nil {
4599
-		c.Fatal(err)
4600
-	}
4601
-	defer os.RemoveAll(tmpDir)
4602
-	mnt1, mnt2 := path.Join(tmpDir, "mnt1"), path.Join(tmpDir, "mnt2")
4603
-	if err := os.Mkdir(mnt1, 0755); err != nil {
4604
-		c.Fatal(err)
4605
-	}
4606
-	if err := os.Mkdir(mnt2, 0755); err != nil {
4607
-		c.Fatal(err)
4608
-	}
4609
-	if err := ioutil.WriteFile(path.Join(mnt1, "test1"), []byte("test1"), 0644); err != nil {
4610
-		c.Fatal(err)
4611
-	}
4612
-	if err := ioutil.WriteFile(path.Join(mnt2, "test2"), []byte("test2"), 0644); err != nil {
4613
-		c.Fatal(err)
4614
-	}
4615
-	testCatFooBar := func(cName string) error {
4616
-		out, _ := dockerCmd(c, "exec", cName, "cat", "/foo/test1")
4617
-		if out != "test1" {
4618
-			return fmt.Errorf("%s not mounted on /foo", mnt1)
4619
-		}
4620
-		out, _ = dockerCmd(c, "exec", cName, "cat", "/bar/test2")
4621
-		if out != "test2" {
4622
-			return fmt.Errorf("%s not mounted on /bar", mnt2)
4623
-		}
4624
-		return nil
4625
-	}
4626
-
4627
-	type testCase struct {
4628
-		equivalents [][]string
4629
-		valid       bool
4630
-		// fn should be nil if valid==false
4631
-		fn func(cName string) error
4632
-	}
4633
-	cases := []testCase{
4634
-		{
4635
-			equivalents: [][]string{
4636
-				{
4637
-					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4638
-					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/bar", mnt2),
4639
-				},
4640
-				{
4641
-					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4642
-					"--mount", fmt.Sprintf("type=bind,src=%s,target=/bar", mnt2),
4643
-				},
4644
-				{
4645
-					"--volume", fmt.Sprintf("%s:/foo", mnt1),
4646
-					"--mount", fmt.Sprintf("type=bind,src=%s,target=/bar", mnt2),
4647
-				},
4648
-			},
4649
-			valid: true,
4650
-			fn:    testCatFooBar,
4651
-		},
4652
-		{
4653
-			equivalents: [][]string{
4654
-				{
4655
-					"--mount", fmt.Sprintf("type=volume,src=%s,dst=/foo", mnt1),
4656
-					"--mount", fmt.Sprintf("type=volume,src=%s,dst=/bar", mnt2),
4657
-				},
4658
-				{
4659
-					"--mount", fmt.Sprintf("type=volume,src=%s,dst=/foo", mnt1),
4660
-					"--mount", fmt.Sprintf("type=volume,src=%s,target=/bar", mnt2),
4661
-				},
4662
-			},
4663
-			valid: false,
4664
-		},
4665
-		{
4666
-			equivalents: [][]string{
4667
-				{
4668
-					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4669
-					"--mount", fmt.Sprintf("type=volume,src=%s,dst=/bar", mnt2),
4670
-				},
4671
-				{
4672
-					"--volume", fmt.Sprintf("%s:/foo", mnt1),
4673
-					"--mount", fmt.Sprintf("type=volume,src=%s,target=/bar", mnt2),
4674
-				},
4675
-			},
4676
-			valid: false,
4677
-			fn:    testCatFooBar,
4678
-		},
4679
-		{
4680
-			equivalents: [][]string{
4681
-				{
4682
-					"--read-only",
4683
-					"--mount", "type=volume,dst=/bar",
4684
-				},
4685
-			},
4686
-			valid: true,
4687
-			fn: func(cName string) error {
4688
-				_, _, err := dockerCmdWithError("exec", cName, "touch", "/bar/icanwritehere")
4689
-				return err
4690
-			},
4691
-		},
4692
-		{
4693
-			equivalents: [][]string{
4694
-				{
4695
-					"--read-only",
4696
-					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4697
-					"--mount", "type=volume,dst=/bar",
4698
-				},
4699
-				{
4700
-					"--read-only",
4701
-					"--volume", fmt.Sprintf("%s:/foo", mnt1),
4702
-					"--mount", "type=volume,dst=/bar",
4703
-				},
4704
-			},
4705
-			valid: true,
4706
-			fn: func(cName string) error {
4707
-				out, _ := dockerCmd(c, "exec", cName, "cat", "/foo/test1")
4708
-				if out != "test1" {
4709
-					return fmt.Errorf("%s not mounted on /foo", mnt1)
4710
-				}
4711
-				_, _, err := dockerCmdWithError("exec", cName, "touch", "/bar/icanwritehere")
4712
-				return err
4713
-			},
4714
-		},
4715
-		{
4716
-			equivalents: [][]string{
4717
-				{
4718
-					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4719
-					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt2),
4720
-				},
4721
-				{
4722
-					"--mount", fmt.Sprintf("type=bind,src=%s,dst=/foo", mnt1),
4723
-					"--mount", fmt.Sprintf("type=bind,src=%s,target=/foo", mnt2),
4724
-				},
4725
-				{
4726
-					"--volume", fmt.Sprintf("%s:/foo", mnt1),
4727
-					"--mount", fmt.Sprintf("type=bind,src=%s,target=/foo", mnt2),
4728
-				},
4729
-			},
4730
-			valid: false,
4731
-		},
4732
-		{
4733
-			equivalents: [][]string{
4734
-				{
4735
-					"--volume", fmt.Sprintf("%s:/foo", mnt1),
4736
-					"--mount", fmt.Sprintf("type=volume,src=%s,target=/foo", mnt2),
4737
-				},
4738
-			},
4739
-			valid: false,
4740
-		},
4741
-		{
4742
-			equivalents: [][]string{
4743
-				{
4744
-					"--mount", "type=volume,target=/foo",
4745
-					"--mount", "type=volume,target=/foo",
4746
-				},
4747
-			},
4748
-			valid: false,
4749
-		},
4750
-	}
4751
-
4752
-	for i, testCase := range cases {
4753
-		for j, opts := range testCase.equivalents {
4754
-			cName := fmt.Sprintf("mount-%d-%d", i, j)
4755
-			_, _, err := dockerCmdWithError(append([]string{"run", "-i", "-d", "--name", cName},
4756
-				append(opts, []string{"busybox", "top"}...)...)...)
4757
-			if testCase.valid {
4758
-				c.Assert(err, check.IsNil,
4759
-					check.Commentf("got error while creating a container with %v (%s)", opts, cName))
4760
-				c.Assert(testCase.fn(cName), check.IsNil,
4761
-					check.Commentf("got error while executing test for %v (%s)", opts, cName))
4762
-				dockerCmd(c, "rm", "-f", cName)
4763
-			} else {
4764
-				c.Assert(err, checker.NotNil,
4765
-					check.Commentf("got nil while creating a container with %v (%s)", opts, cName))
4766
-			}
4767
-		}
4768
-	}
4769
-}
4770
-
4771 4593
 func (s *DockerSuite) TestRunWindowsWithCPUCount(c *check.C) {
4772 4594
 	testRequires(c, DaemonIsWindows)
4773 4595
 
... ...
@@ -123,7 +123,7 @@ func (s *DockerSwarmSuite) TestServiceCreateWithSecretSourceTarget(c *check.C) {
123 123
 
124 124
 func (s *DockerSwarmSuite) TestServiceCreateMountTmpfs(c *check.C) {
125 125
 	d := s.AddDaemon(c, true, true)
126
-	out, err := d.Cmd("service", "create", "--mount", "type=tmpfs,target=/foo", "busybox", "sh", "-c", "mount | grep foo; tail -f /dev/null")
126
+	out, err := d.Cmd("service", "create", "--mount", "type=tmpfs,target=/foo,tmpfs-size=1MB", "busybox", "sh", "-c", "mount | grep foo; tail -f /dev/null")
127 127
 	c.Assert(err, checker.IsNil, check.Commentf(out))
128 128
 	id := strings.TrimSpace(out)
129 129
 
... ...
@@ -152,6 +152,8 @@ func (s *DockerSwarmSuite) TestServiceCreateMountTmpfs(c *check.C) {
152 152
 	c.Assert(mountConfig[0].Source, checker.Equals, "")
153 153
 	c.Assert(mountConfig[0].Target, checker.Equals, "/foo")
154 154
 	c.Assert(mountConfig[0].Type, checker.Equals, mount.TypeTmpfs)
155
+	c.Assert(mountConfig[0].TmpfsOptions, checker.NotNil)
156
+	c.Assert(mountConfig[0].TmpfsOptions.SizeBytes, checker.Equals, int64(1048576))
155 157
 
156 158
 	// check container mounts actual
157 159
 	out, err = s.nodeCmd(c, task.NodeID, "inspect", "--format", "{{json .Mounts}}", task.Status.ContainerStatus.ContainerID)
... ...
@@ -169,4 +171,5 @@ func (s *DockerSwarmSuite) TestServiceCreateMountTmpfs(c *check.C) {
169 169
 	out, err = s.nodeCmd(c, task.NodeID, "logs", task.Status.ContainerStatus.ContainerID)
170 170
 	c.Assert(err, checker.IsNil, check.Commentf(out))
171 171
 	c.Assert(strings.TrimSpace(out), checker.HasPrefix, "tmpfs on /foo type tmpfs")
172
+	c.Assert(strings.TrimSpace(out), checker.Contains, "size=1024k")
172 173
 }
... ...
@@ -226,88 +226,6 @@ func (s *DockerSwarmSuite) TestSwarmNodeTaskListFilter(c *check.C) {
226 226
 	c.Assert(out, checker.Not(checker.Contains), name+".1")
227 227
 	c.Assert(out, checker.Not(checker.Contains), name+".2")
228 228
 	c.Assert(out, checker.Not(checker.Contains), name+".3")
229
-
230
-	out, err = d.Cmd("node", "ps", "--filter", "desired-state=running", "self")
231
-	c.Assert(err, checker.IsNil)
232
-	c.Assert(out, checker.Contains, name+".1")
233
-	c.Assert(out, checker.Contains, name+".2")
234
-	c.Assert(out, checker.Contains, name+".3")
235
-
236
-	out, err = d.Cmd("node", "ps", "--filter", "desired-state=shutdown", "self")
237
-	c.Assert(err, checker.IsNil)
238
-	c.Assert(out, checker.Not(checker.Contains), name+".1")
239
-	c.Assert(out, checker.Not(checker.Contains), name+".2")
240
-	c.Assert(out, checker.Not(checker.Contains), name+".3")
241
-}
242
-
243
-func (s *DockerSwarmSuite) TestSwarmServiceTaskListAll(c *check.C) {
244
-	d := s.AddDaemon(c, true, true)
245
-
246
-	name := "service-task-list-1"
247
-	out, err := d.Cmd("service", "create", "--name", name, "--replicas=3", "busybox", "top")
248
-	c.Assert(err, checker.IsNil)
249
-	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
250
-
251
-	// make sure task has been deployed.
252
-	waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 3)
253
-
254
-	out, err = d.Cmd("service", "ps", name)
255
-	c.Assert(err, checker.IsNil)
256
-	c.Assert(out, checker.Contains, name+".1")
257
-	c.Assert(out, checker.Contains, name+".2")
258
-	c.Assert(out, checker.Contains, name+".3")
259
-
260
-	// Get the last container id so we can restart it to cause a task error in the history
261
-	containerID, err := d.Cmd("ps", "-q", "-l")
262
-	c.Assert(err, checker.IsNil)
263
-
264
-	_, err = d.Cmd("stop", strings.TrimSpace(containerID))
265
-	c.Assert(err, checker.IsNil)
266
-
267
-	waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 3)
268
-
269
-	out, err = d.Cmd("service", "ps", name)
270
-	c.Assert(err, checker.IsNil)
271
-	c.Assert(out, checker.Count, name, 3)
272
-
273
-	out, err = d.Cmd("service", "ps", name, "-a")
274
-	c.Assert(err, checker.IsNil)
275
-	c.Assert(out, checker.Count, name, 4)
276
-}
277
-
278
-func (s *DockerSwarmSuite) TestSwarmNodeTaskListAll(c *check.C) {
279
-	d := s.AddDaemon(c, true, true)
280
-
281
-	name := "node-task-list"
282
-	out, err := d.Cmd("service", "create", "--name", name, "--replicas=3", "busybox", "top")
283
-	c.Assert(err, checker.IsNil)
284
-	c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "")
285
-
286
-	// make sure task has been deployed.
287
-	waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 3)
288
-
289
-	out, err = d.Cmd("service", "ps", name)
290
-	c.Assert(err, checker.IsNil)
291
-	c.Assert(out, checker.Contains, name+".1")
292
-	c.Assert(out, checker.Contains, name+".2")
293
-	c.Assert(out, checker.Contains, name+".3")
294
-
295
-	// Get the last container id so we can restart it to cause a task error in the history
296
-	containerID, err := d.Cmd("ps", "-q", "-l")
297
-	c.Assert(err, checker.IsNil)
298
-
299
-	_, err = d.Cmd("stop", strings.TrimSpace(containerID))
300
-	c.Assert(err, checker.IsNil)
301
-
302
-	waitAndAssert(c, defaultReconciliationTimeout, d.checkActiveContainerCount, checker.Equals, 3)
303
-
304
-	out, err = d.Cmd("node", "ps", "self")
305
-	c.Assert(err, checker.IsNil)
306
-	c.Assert(out, checker.Count, name, 3)
307
-
308
-	out, err = d.Cmd("node", "ps", "self", "-a")
309
-	c.Assert(err, checker.IsNil)
310
-	c.Assert(out, checker.Count, name, 4)
311 229
 }
312 230
 
313 231
 // Test case for #25375
... ...
@@ -405,13 +405,8 @@ func (clnt *client) getContainerLastEventSinceTime(id string, tsp *timestamp.Tim
405 405
 			logrus.Errorf("libcontainerd: failed to get container event for %s: %q", id, err)
406 406
 			return nil, err
407 407
 		}
408
-
409
-		logrus.Debugf("libcontainerd: received past event %#v", e)
410
-
411
-		switch e.Type {
412
-		case StateExit, StatePause, StateResume:
413
-			ev = e
414
-		}
408
+		ev = e
409
+		logrus.Debugf("libcontainerd: received past event %#v", ev)
415 410
 	}
416 411
 
417 412
 	return ev, nil
... ...
@@ -456,30 +451,36 @@ func (clnt *client) Restore(containerID string, attachStdio StdioCallback, optio
456 456
 	// Get its last event
457 457
 	ev, eerr := clnt.getContainerLastEvent(containerID)
458 458
 	if err != nil || cont.Status == "Stopped" {
459
-		if err != nil && !strings.Contains(err.Error(), "container not found") {
460
-			// Legitimate error
461
-			return err
459
+		if err != nil {
460
+			logrus.Warnf("libcontainerd: failed to retrieve container %s state: %v", containerID, err)
462 461
 		}
463
-
464
-		if ev == nil {
465
-			if _, err := clnt.getContainer(containerID); err == nil {
466
-				// If ev is nil and the container is running in containerd,
467
-				// we already consumed all the event of the
468
-				// container, included the "exit" one.
469
-				// Thus we return to avoid overriding the Exit Code.
470
-				logrus.Warnf("libcontainerd: restore was called on a fully synced container (%s)", containerID)
471
-				return nil
472
-			}
473
-			// the container is not running so we need to fix the state within docker
474
-			ev = &containerd.Event{
475
-				Type:   StateExit,
476
-				Status: 1,
462
+		if ev != nil && (ev.Pid != InitFriendlyName || ev.Type != StateExit) {
463
+			// Wait a while for the exit event
464
+			timeout := time.NewTimer(10 * time.Second)
465
+			tick := time.NewTicker(100 * time.Millisecond)
466
+		stop:
467
+			for {
468
+				select {
469
+				case <-timeout.C:
470
+					break stop
471
+				case <-tick.C:
472
+					ev, eerr = clnt.getContainerLastEvent(containerID)
473
+					if eerr != nil {
474
+						break stop
475
+					}
476
+					if ev != nil && ev.Pid == InitFriendlyName && ev.Type == StateExit {
477
+						break stop
478
+					}
479
+				}
477 480
 			}
481
+			timeout.Stop()
482
+			tick.Stop()
478 483
 		}
479 484
 
480
-		// get the exit status for this container
481
-		ec := uint32(0)
482
-		if eerr == nil && ev.Type == StateExit {
485
+		// get the exit status for this container, if we don't have
486
+		// one, indicate an error
487
+		ec := uint32(255)
488
+		if eerr == nil && ev != nil && ev.Pid == InitFriendlyName && ev.Type == StateExit {
483 489
 			ec = ev.Status
484 490
 		}
485 491
 		clnt.setExited(containerID, ec)
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"os"
9 9
 	"path/filepath"
10 10
 	goruntime "runtime"
11
+	"strings"
11 12
 	"time"
12 13
 
13 14
 	containerd "github.com/docker/containerd/api/grpc/types"
... ...
@@ -86,6 +87,9 @@ func (p *process) sendCloseStdin() error {
86 86
 		Pid:        p.friendlyName,
87 87
 		CloseStdin: true,
88 88
 	})
89
+	if err != nil && (strings.Contains(err.Error(), "container not found") || strings.Contains(err.Error(), "process not found")) {
90
+		return nil
91
+	}
89 92
 	return err
90 93
 }
91 94
 
... ...
@@ -83,7 +83,7 @@ set as the **URL**, the repository is cloned locally and then sent as the contex
83 83
    Users pass these values at build-time. Docker uses the `buildargs` as the
84 84
    environment context for command(s) run via the Dockerfile's `RUN` instruction
85 85
    or for variable expansion in other Dockerfile instructions. This is not meant
86
-   for passing secret values. [Read more about the buildargs instruction](/reference/builder/#arg)
86
+   for passing secret values. [Read more about the buildargs instruction](https://docs.docker.com/engine/reference/builder/#arg)
87 87
 
88 88
 **--force-rm**=*true*|*false*
89 89
    Always remove intermediate containers, even after unsuccessful builds. The default is *false*.
... ...
@@ -56,7 +56,6 @@ docker-create - Create a new container
56 56
 [**--memory-reservation**[=*MEMORY-RESERVATION*]]
57 57
 [**--memory-swap**[=*LIMIT*]]
58 58
 [**--memory-swappiness**[=*MEMORY-SWAPPINESS*]]
59
-[**--mount**[=*MOUNT*]]
60 59
 [**--name**[=*NAME*]]
61 60
 [**--network-alias**[=*[]*]]
62 61
 [**--network**[=*"bridge"*]]
... ...
@@ -20,7 +20,7 @@ do not specify a `SERVER`, the command uses Docker's public registry located at
20 20
 `docker login` requires user to use `sudo` or be `root`, except when:
21 21
 
22 22
 1.  connecting to  a remote daemon, such as a `docker-machine` provisioned `docker engine`.
23
-2.  user is added to the `docker` group.  This will impact the security of your system; the `docker` group is `root` equivalent.  See [Docker Daemon Attack Surface](https://docs.docker.com/articles/security/#docker-daemon-attack-surface) for details.
23
+2.  user is added to the `docker` group.  This will impact the security of your system; the `docker` group is `root` equivalent.  See [Docker Daemon Attack Surface](https://docs.docker.com/engine/articles/security/#docker-daemon-attack-surface) for details.
24 24
 
25 25
 You can log into any public or private repository for which you have
26 26
 credentials.  When you log in, the command stores encoded credentials in
... ...
@@ -18,7 +18,7 @@ that it is being suspended, and subsequently resumed. On Windows, only Hyper-V
18 18
 containers can be paused.
19 19
 
20 20
 See the [cgroups freezer documentation]
21
-(https://www.kernel.org/doc/Documentation/cgroups/freezer-subsystem.txt) for
21
+(https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt) for
22 22
 further details.
23 23
 
24 24
 # OPTIONS
... ...
@@ -58,7 +58,6 @@ docker-run - Run a command in a new container
58 58
 [**--memory-reservation**[=*MEMORY-RESERVATION*]]
59 59
 [**--memory-swap**[=*LIMIT*]]
60 60
 [**--memory-swappiness**[=*MEMORY-SWAPPINESS*]]
61
-[**--mount**[=*MOUNT*]]
62 61
 [**--name**[=*NAME*]]
63 62
 [**--network-alias**[=*[]*]]
64 63
 [**--network**[=*"bridge"*]]
... ...
@@ -14,7 +14,7 @@ The `docker unpause` command un-suspends all processes in a container.
14 14
 On Linux, it does this using the cgroups freezer.
15 15
 
16 16
 See the [cgroups freezer documentation]
17
-(https://www.kernel.org/doc/Documentation/cgroups/freezer-subsystem.txt) for
17
+(https://www.kernel.org/doc/Documentation/cgroup-v1/freezer-subsystem.txt) for
18 18
 further details.
19 19
 
20 20
 # OPTIONS
... ...
@@ -59,4 +59,4 @@ To view all available fields, you can use the format `{{json .}}`.
59 59
 # HISTORY
60 60
 June 2014, updated by Sven Dowideit <SvenDowideit@home.org.au>
61 61
 June 2015, updated by John Howard <jhoward@microsoft.com>
62
-June 2015, updated by Patrick Hemmer <patrick.hemmer@gmail.com
62
+June 2015, updated by Patrick Hemmer <patrick.hemmer@gmail.com>
63 63
deleted file mode 100644
... ...
@@ -1,15 +0,0 @@
1
-// +build cgo
2
-
3
-package graphdb
4
-
5
-import "database/sql"
6
-
7
-// NewSqliteConn opens a connection to a sqlite
8
-// database.
9
-func NewSqliteConn(root string) (*Database, error) {
10
-	conn, err := sql.Open("sqlite3", root)
11
-	if err != nil {
12
-		return nil, err
13
-	}
14
-	return NewDatabase(conn)
15
-}
16 1
new file mode 100644
... ...
@@ -0,0 +1,19 @@
0
+// +build cgo
1
+
2
+package graphdb
3
+
4
+import (
5
+	"database/sql"
6
+
7
+	_ "github.com/mattn/go-sqlite3" // registers sqlite
8
+)
9
+
10
+// NewSqliteConn opens a connection to a sqlite
11
+// database.
12
+func NewSqliteConn(root string) (*Database, error) {
13
+	conn, err := sql.Open("sqlite3", root)
14
+	if err != nil {
15
+		return nil, err
16
+	}
17
+	return NewDatabase(conn)
18
+}
0 19
deleted file mode 100644
... ...
@@ -1,7 +0,0 @@
1
-// +build cgo,!windows
2
-
3
-package graphdb
4
-
5
-import (
6
-	_ "github.com/mattn/go-sqlite3" // registers sqlite
7
-)
8 1
deleted file mode 100644
... ...
@@ -1,7 +0,0 @@
1
-// +build cgo,windows
2
-
3
-package graphdb
4
-
5
-import (
6
-	_ "github.com/mattn/go-sqlite3" // registers sqlite
7
-)
8 1
deleted file mode 100644
... ...
@@ -1,8 +0,0 @@
1
-// +build !cgo
2
-
3
-package graphdb
4
-
5
-// NewSqliteConn return a new sqlite connection.
6
-func NewSqliteConn(root string) (*Database, error) {
7
-	panic("Not implemented")
8
-}
9 1
deleted file mode 100644
... ...
@@ -1,551 +0,0 @@
1
-package graphdb
2
-
3
-import (
4
-	"database/sql"
5
-	"fmt"
6
-	"path"
7
-	"strings"
8
-	"sync"
9
-)
10
-
11
-const (
12
-	createEntityTable = `
13
-    CREATE TABLE IF NOT EXISTS entity (
14
-        id text NOT NULL PRIMARY KEY
15
-    );`
16
-
17
-	createEdgeTable = `
18
-    CREATE TABLE IF NOT EXISTS edge (
19
-        "entity_id" text NOT NULL,
20
-        "parent_id" text NULL,
21
-        "name" text NOT NULL,
22
-        CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
23
-        CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
24
-        );
25
-    `
26
-
27
-	createEdgeIndices = `
28
-    CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name);
29
-    `
30
-)
31
-
32
-// Entity with a unique id.
33
-type Entity struct {
34
-	id string
35
-}
36
-
37
-// An Edge connects two entities together.
38
-type Edge struct {
39
-	EntityID string
40
-	Name     string
41
-	ParentID string
42
-}
43
-
44
-// Entities stores the list of entities.
45
-type Entities map[string]*Entity
46
-
47
-// Edges stores the relationships between entities.
48
-type Edges []*Edge
49
-
50
-// WalkFunc is a function invoked to process an individual entity.
51
-type WalkFunc func(fullPath string, entity *Entity) error
52
-
53
-// Database is a graph database for storing entities and their relationships.
54
-type Database struct {
55
-	conn *sql.DB
56
-	mux  sync.RWMutex
57
-}
58
-
59
-// IsNonUniqueNameError processes the error to check if it's caused by
60
-// a constraint violation.
61
-// This is necessary because the error isn't the same across various
62
-// sqlite versions.
63
-func IsNonUniqueNameError(err error) bool {
64
-	str := err.Error()
65
-	// sqlite 3.7.17-1ubuntu1 returns:
66
-	// Set failure: Abort due to constraint violation: columns parent_id, name are not unique
67
-	if strings.HasSuffix(str, "name are not unique") {
68
-		return true
69
-	}
70
-	// sqlite-3.8.3-1.fc20 returns:
71
-	// Set failure: Abort due to constraint violation: UNIQUE constraint failed: edge.parent_id, edge.name
72
-	if strings.Contains(str, "UNIQUE constraint failed") && strings.Contains(str, "edge.name") {
73
-		return true
74
-	}
75
-	// sqlite-3.6.20-1.el6 returns:
76
-	// Set failure: Abort due to constraint violation: constraint failed
77
-	if strings.HasSuffix(str, "constraint failed") {
78
-		return true
79
-	}
80
-	return false
81
-}
82
-
83
-// NewDatabase creates a new graph database initialized with a root entity.
84
-func NewDatabase(conn *sql.DB) (*Database, error) {
85
-	if conn == nil {
86
-		return nil, fmt.Errorf("Database connection cannot be nil")
87
-	}
88
-	db := &Database{conn: conn}
89
-
90
-	// Create root entities
91
-	tx, err := conn.Begin()
92
-	if err != nil {
93
-		return nil, err
94
-	}
95
-
96
-	if _, err := tx.Exec(createEntityTable); err != nil {
97
-		return nil, err
98
-	}
99
-	if _, err := tx.Exec(createEdgeTable); err != nil {
100
-		return nil, err
101
-	}
102
-	if _, err := tx.Exec(createEdgeIndices); err != nil {
103
-		return nil, err
104
-	}
105
-
106
-	if _, err := tx.Exec("DELETE FROM entity where id = ?", "0"); err != nil {
107
-		tx.Rollback()
108
-		return nil, err
109
-	}
110
-
111
-	if _, err := tx.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
112
-		tx.Rollback()
113
-		return nil, err
114
-	}
115
-
116
-	if _, err := tx.Exec("DELETE FROM edge where entity_id=? and name=?", "0", "/"); err != nil {
117
-		tx.Rollback()
118
-		return nil, err
119
-	}
120
-
121
-	if _, err := tx.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
122
-		tx.Rollback()
123
-		return nil, err
124
-	}
125
-
126
-	if err := tx.Commit(); err != nil {
127
-		return nil, err
128
-	}
129
-
130
-	return db, nil
131
-}
132
-
133
-// Close the underlying connection to the database.
134
-func (db *Database) Close() error {
135
-	return db.conn.Close()
136
-}
137
-
138
-// Set the entity id for a given path.
139
-func (db *Database) Set(fullPath, id string) (*Entity, error) {
140
-	db.mux.Lock()
141
-	defer db.mux.Unlock()
142
-
143
-	tx, err := db.conn.Begin()
144
-	if err != nil {
145
-		return nil, err
146
-	}
147
-
148
-	var entityID string
149
-	if err := tx.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityID); err != nil {
150
-		if err == sql.ErrNoRows {
151
-			if _, err := tx.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
152
-				tx.Rollback()
153
-				return nil, err
154
-			}
155
-		} else {
156
-			tx.Rollback()
157
-			return nil, err
158
-		}
159
-	}
160
-	e := &Entity{id}
161
-
162
-	parentPath, name := splitPath(fullPath)
163
-	if err := db.setEdge(parentPath, name, e, tx); err != nil {
164
-		tx.Rollback()
165
-		return nil, err
166
-	}
167
-
168
-	if err := tx.Commit(); err != nil {
169
-		return nil, err
170
-	}
171
-	return e, nil
172
-}
173
-
174
-// Exists returns true if a name already exists in the database.
175
-func (db *Database) Exists(name string) bool {
176
-	db.mux.RLock()
177
-	defer db.mux.RUnlock()
178
-
179
-	e, err := db.get(name)
180
-	if err != nil {
181
-		return false
182
-	}
183
-	return e != nil
184
-}
185
-
186
-func (db *Database) setEdge(parentPath, name string, e *Entity, tx *sql.Tx) error {
187
-	parent, err := db.get(parentPath)
188
-	if err != nil {
189
-		return err
190
-	}
191
-	if parent.id == e.id {
192
-		return fmt.Errorf("Cannot set self as child")
193
-	}
194
-
195
-	if _, err := tx.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
196
-		return err
197
-	}
198
-	return nil
199
-}
200
-
201
-// RootEntity returns the root "/" entity for the database.
202
-func (db *Database) RootEntity() *Entity {
203
-	return &Entity{
204
-		id: "0",
205
-	}
206
-}
207
-
208
-// Get returns the entity for a given path.
209
-func (db *Database) Get(name string) *Entity {
210
-	db.mux.RLock()
211
-	defer db.mux.RUnlock()
212
-
213
-	e, err := db.get(name)
214
-	if err != nil {
215
-		return nil
216
-	}
217
-	return e
218
-}
219
-
220
-func (db *Database) get(name string) (*Entity, error) {
221
-	e := db.RootEntity()
222
-	// We always know the root name so return it if
223
-	// it is requested
224
-	if name == "/" {
225
-		return e, nil
226
-	}
227
-
228
-	parts := split(name)
229
-	for i := 1; i < len(parts); i++ {
230
-		p := parts[i]
231
-		if p == "" {
232
-			continue
233
-		}
234
-
235
-		next := db.child(e, p)
236
-		if next == nil {
237
-			return nil, fmt.Errorf("Cannot find child for %s", name)
238
-		}
239
-		e = next
240
-	}
241
-	return e, nil
242
-
243
-}
244
-
245
-// List all entities by from the name.
246
-// The key will be the full path of the entity.
247
-func (db *Database) List(name string, depth int) Entities {
248
-	db.mux.RLock()
249
-	defer db.mux.RUnlock()
250
-
251
-	out := Entities{}
252
-	e, err := db.get(name)
253
-	if err != nil {
254
-		return out
255
-	}
256
-
257
-	children, err := db.children(e, name, depth, nil)
258
-	if err != nil {
259
-		return out
260
-	}
261
-
262
-	for _, c := range children {
263
-		out[c.FullPath] = c.Entity
264
-	}
265
-	return out
266
-}
267
-
268
-// Walk through the child graph of an entity, calling walkFunc for each child entity.
269
-// It is safe for walkFunc to call graph functions.
270
-func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
271
-	children, err := db.Children(name, depth)
272
-	if err != nil {
273
-		return err
274
-	}
275
-
276
-	// Note: the database lock must not be held while calling walkFunc
277
-	for _, c := range children {
278
-		if err := walkFunc(c.FullPath, c.Entity); err != nil {
279
-			return err
280
-		}
281
-	}
282
-	return nil
283
-}
284
-
285
-// Children returns the children of the specified entity.
286
-func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
287
-	db.mux.RLock()
288
-	defer db.mux.RUnlock()
289
-
290
-	e, err := db.get(name)
291
-	if err != nil {
292
-		return nil, err
293
-	}
294
-
295
-	return db.children(e, name, depth, nil)
296
-}
297
-
298
-// Parents returns the parents of a specified entity.
299
-func (db *Database) Parents(name string) ([]string, error) {
300
-	db.mux.RLock()
301
-	defer db.mux.RUnlock()
302
-
303
-	e, err := db.get(name)
304
-	if err != nil {
305
-		return nil, err
306
-	}
307
-	return db.parents(e)
308
-}
309
-
310
-// Refs returns the reference count for a specified id.
311
-func (db *Database) Refs(id string) int {
312
-	db.mux.RLock()
313
-	defer db.mux.RUnlock()
314
-
315
-	var count int
316
-	if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
317
-		return 0
318
-	}
319
-	return count
320
-}
321
-
322
-// RefPaths returns all the id's path references.
323
-func (db *Database) RefPaths(id string) Edges {
324
-	db.mux.RLock()
325
-	defer db.mux.RUnlock()
326
-
327
-	refs := Edges{}
328
-
329
-	rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
330
-	if err != nil {
331
-		return refs
332
-	}
333
-	defer rows.Close()
334
-
335
-	for rows.Next() {
336
-		var name string
337
-		var parentID string
338
-		if err := rows.Scan(&name, &parentID); err != nil {
339
-			return refs
340
-		}
341
-		refs = append(refs, &Edge{
342
-			EntityID: id,
343
-			Name:     name,
344
-			ParentID: parentID,
345
-		})
346
-	}
347
-	return refs
348
-}
349
-
350
-// Delete the reference to an entity at a given path.
351
-func (db *Database) Delete(name string) error {
352
-	db.mux.Lock()
353
-	defer db.mux.Unlock()
354
-
355
-	if name == "/" {
356
-		return fmt.Errorf("Cannot delete root entity")
357
-	}
358
-
359
-	parentPath, n := splitPath(name)
360
-	parent, err := db.get(parentPath)
361
-	if err != nil {
362
-		return err
363
-	}
364
-
365
-	if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
366
-		return err
367
-	}
368
-	return nil
369
-}
370
-
371
-// Purge removes the entity with the specified id
372
-// Walk the graph to make sure all references to the entity
373
-// are removed and return the number of references removed
374
-func (db *Database) Purge(id string) (int, error) {
375
-	db.mux.Lock()
376
-	defer db.mux.Unlock()
377
-
378
-	tx, err := db.conn.Begin()
379
-	if err != nil {
380
-		return -1, err
381
-	}
382
-
383
-	// Delete all edges
384
-	rows, err := tx.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
385
-	if err != nil {
386
-		tx.Rollback()
387
-		return -1, err
388
-	}
389
-	changes, err := rows.RowsAffected()
390
-	if err != nil {
391
-		return -1, err
392
-	}
393
-
394
-	// Clear who's using this id as parent
395
-	refs, err := tx.Exec("DELETE FROM edge WHERE parent_id = ?;", id)
396
-	if err != nil {
397
-		tx.Rollback()
398
-		return -1, err
399
-	}
400
-	refsCount, err := refs.RowsAffected()
401
-	if err != nil {
402
-		return -1, err
403
-	}
404
-
405
-	// Delete entity
406
-	if _, err := tx.Exec("DELETE FROM entity where id = ?;", id); err != nil {
407
-		tx.Rollback()
408
-		return -1, err
409
-	}
410
-
411
-	if err := tx.Commit(); err != nil {
412
-		return -1, err
413
-	}
414
-
415
-	return int(changes + refsCount), nil
416
-}
417
-
418
-// Rename an edge for a given path
419
-func (db *Database) Rename(currentName, newName string) error {
420
-	db.mux.Lock()
421
-	defer db.mux.Unlock()
422
-
423
-	parentPath, name := splitPath(currentName)
424
-	newParentPath, newEdgeName := splitPath(newName)
425
-
426
-	if parentPath != newParentPath {
427
-		return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
428
-	}
429
-
430
-	parent, err := db.get(parentPath)
431
-	if err != nil {
432
-		return err
433
-	}
434
-
435
-	rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
436
-	if err != nil {
437
-		return err
438
-	}
439
-	i, err := rows.RowsAffected()
440
-	if err != nil {
441
-		return err
442
-	}
443
-	if i == 0 {
444
-		return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
445
-	}
446
-	return nil
447
-}
448
-
449
-// WalkMeta stores the walk metadata.
450
-type WalkMeta struct {
451
-	Parent   *Entity
452
-	Entity   *Entity
453
-	FullPath string
454
-	Edge     *Edge
455
-}
456
-
457
-func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
458
-	if e == nil {
459
-		return entities, nil
460
-	}
461
-
462
-	rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
463
-	if err != nil {
464
-		return nil, err
465
-	}
466
-	defer rows.Close()
467
-
468
-	for rows.Next() {
469
-		var entityID, entityName string
470
-		if err := rows.Scan(&entityID, &entityName); err != nil {
471
-			return nil, err
472
-		}
473
-		child := &Entity{entityID}
474
-		edge := &Edge{
475
-			ParentID: e.id,
476
-			Name:     entityName,
477
-			EntityID: child.id,
478
-		}
479
-
480
-		meta := WalkMeta{
481
-			Parent:   e,
482
-			Entity:   child,
483
-			FullPath: path.Join(name, edge.Name),
484
-			Edge:     edge,
485
-		}
486
-
487
-		entities = append(entities, meta)
488
-
489
-		if depth != 0 {
490
-			nDepth := depth
491
-			if depth != -1 {
492
-				nDepth--
493
-			}
494
-			entities, err = db.children(child, meta.FullPath, nDepth, entities)
495
-			if err != nil {
496
-				return nil, err
497
-			}
498
-		}
499
-	}
500
-
501
-	return entities, nil
502
-}
503
-
504
-func (db *Database) parents(e *Entity) (parents []string, err error) {
505
-	if e == nil {
506
-		return parents, nil
507
-	}
508
-
509
-	rows, err := db.conn.Query("SELECT parent_id FROM edge where entity_id = ?;", e.id)
510
-	if err != nil {
511
-		return nil, err
512
-	}
513
-	defer rows.Close()
514
-
515
-	for rows.Next() {
516
-		var parentID string
517
-		if err := rows.Scan(&parentID); err != nil {
518
-			return nil, err
519
-		}
520
-		parents = append(parents, parentID)
521
-	}
522
-
523
-	return parents, nil
524
-}
525
-
526
-// Return the entity based on the parent path and name.
527
-func (db *Database) child(parent *Entity, name string) *Entity {
528
-	var id string
529
-	if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
530
-		return nil
531
-	}
532
-	return &Entity{id}
533
-}
534
-
535
-// ID returns the id used to reference this entity.
536
-func (e *Entity) ID() string {
537
-	return e.id
538
-}
539
-
540
-// Paths returns the paths sorted by depth.
541
-func (e Entities) Paths() []string {
542
-	out := make([]string, len(e))
543
-	var i int
544
-	for k := range e {
545
-		out[i] = k
546
-		i++
547
-	}
548
-	sortByDepth(out)
549
-
550
-	return out
551
-}
552 1
new file mode 100644
... ...
@@ -0,0 +1,551 @@
0
+package graphdb
1
+
2
+import (
3
+	"database/sql"
4
+	"fmt"
5
+	"path"
6
+	"strings"
7
+	"sync"
8
+)
9
+
10
+const (
11
+	createEntityTable = `
12
+    CREATE TABLE IF NOT EXISTS entity (
13
+        id text NOT NULL PRIMARY KEY
14
+    );`
15
+
16
+	createEdgeTable = `
17
+    CREATE TABLE IF NOT EXISTS edge (
18
+        "entity_id" text NOT NULL,
19
+        "parent_id" text NULL,
20
+        "name" text NOT NULL,
21
+        CONSTRAINT "parent_fk" FOREIGN KEY ("parent_id") REFERENCES "entity" ("id"),
22
+        CONSTRAINT "entity_fk" FOREIGN KEY ("entity_id") REFERENCES "entity" ("id")
23
+        );
24
+    `
25
+
26
+	createEdgeIndices = `
27
+    CREATE UNIQUE INDEX IF NOT EXISTS "name_parent_ix" ON "edge" (parent_id, name);
28
+    `
29
+)
30
+
31
+// Entity with a unique id.
32
+type Entity struct {
33
+	id string
34
+}
35
+
36
+// An Edge connects two entities together.
37
+type Edge struct {
38
+	EntityID string
39
+	Name     string
40
+	ParentID string
41
+}
42
+
43
+// Entities stores the list of entities.
44
+type Entities map[string]*Entity
45
+
46
+// Edges stores the relationships between entities.
47
+type Edges []*Edge
48
+
49
+// WalkFunc is a function invoked to process an individual entity.
50
+type WalkFunc func(fullPath string, entity *Entity) error
51
+
52
+// Database is a graph database for storing entities and their relationships.
53
+type Database struct {
54
+	conn *sql.DB
55
+	mux  sync.RWMutex
56
+}
57
+
58
+// IsNonUniqueNameError processes the error to check if it's caused by
59
+// a constraint violation.
60
+// This is necessary because the error isn't the same across various
61
+// sqlite versions.
62
+func IsNonUniqueNameError(err error) bool {
63
+	str := err.Error()
64
+	// sqlite 3.7.17-1ubuntu1 returns:
65
+	// Set failure: Abort due to constraint violation: columns parent_id, name are not unique
66
+	if strings.HasSuffix(str, "name are not unique") {
67
+		return true
68
+	}
69
+	// sqlite-3.8.3-1.fc20 returns:
70
+	// Set failure: Abort due to constraint violation: UNIQUE constraint failed: edge.parent_id, edge.name
71
+	if strings.Contains(str, "UNIQUE constraint failed") && strings.Contains(str, "edge.name") {
72
+		return true
73
+	}
74
+	// sqlite-3.6.20-1.el6 returns:
75
+	// Set failure: Abort due to constraint violation: constraint failed
76
+	if strings.HasSuffix(str, "constraint failed") {
77
+		return true
78
+	}
79
+	return false
80
+}
81
+
82
+// NewDatabase creates a new graph database initialized with a root entity.
83
+func NewDatabase(conn *sql.DB) (*Database, error) {
84
+	if conn == nil {
85
+		return nil, fmt.Errorf("Database connection cannot be nil")
86
+	}
87
+	db := &Database{conn: conn}
88
+
89
+	// Create root entities
90
+	tx, err := conn.Begin()
91
+	if err != nil {
92
+		return nil, err
93
+	}
94
+
95
+	if _, err := tx.Exec(createEntityTable); err != nil {
96
+		return nil, err
97
+	}
98
+	if _, err := tx.Exec(createEdgeTable); err != nil {
99
+		return nil, err
100
+	}
101
+	if _, err := tx.Exec(createEdgeIndices); err != nil {
102
+		return nil, err
103
+	}
104
+
105
+	if _, err := tx.Exec("DELETE FROM entity where id = ?", "0"); err != nil {
106
+		tx.Rollback()
107
+		return nil, err
108
+	}
109
+
110
+	if _, err := tx.Exec("INSERT INTO entity (id) VALUES (?);", "0"); err != nil {
111
+		tx.Rollback()
112
+		return nil, err
113
+	}
114
+
115
+	if _, err := tx.Exec("DELETE FROM edge where entity_id=? and name=?", "0", "/"); err != nil {
116
+		tx.Rollback()
117
+		return nil, err
118
+	}
119
+
120
+	if _, err := tx.Exec("INSERT INTO edge (entity_id, name) VALUES(?,?);", "0", "/"); err != nil {
121
+		tx.Rollback()
122
+		return nil, err
123
+	}
124
+
125
+	if err := tx.Commit(); err != nil {
126
+		return nil, err
127
+	}
128
+
129
+	return db, nil
130
+}
131
+
132
+// Close the underlying connection to the database.
133
+func (db *Database) Close() error {
134
+	return db.conn.Close()
135
+}
136
+
137
+// Set the entity id for a given path.
138
+func (db *Database) Set(fullPath, id string) (*Entity, error) {
139
+	db.mux.Lock()
140
+	defer db.mux.Unlock()
141
+
142
+	tx, err := db.conn.Begin()
143
+	if err != nil {
144
+		return nil, err
145
+	}
146
+
147
+	var entityID string
148
+	if err := tx.QueryRow("SELECT id FROM entity WHERE id = ?;", id).Scan(&entityID); err != nil {
149
+		if err == sql.ErrNoRows {
150
+			if _, err := tx.Exec("INSERT INTO entity (id) VALUES(?);", id); err != nil {
151
+				tx.Rollback()
152
+				return nil, err
153
+			}
154
+		} else {
155
+			tx.Rollback()
156
+			return nil, err
157
+		}
158
+	}
159
+	e := &Entity{id}
160
+
161
+	parentPath, name := splitPath(fullPath)
162
+	if err := db.setEdge(parentPath, name, e, tx); err != nil {
163
+		tx.Rollback()
164
+		return nil, err
165
+	}
166
+
167
+	if err := tx.Commit(); err != nil {
168
+		return nil, err
169
+	}
170
+	return e, nil
171
+}
172
+
173
+// Exists returns true if a name already exists in the database.
174
+func (db *Database) Exists(name string) bool {
175
+	db.mux.RLock()
176
+	defer db.mux.RUnlock()
177
+
178
+	e, err := db.get(name)
179
+	if err != nil {
180
+		return false
181
+	}
182
+	return e != nil
183
+}
184
+
185
+func (db *Database) setEdge(parentPath, name string, e *Entity, tx *sql.Tx) error {
186
+	parent, err := db.get(parentPath)
187
+	if err != nil {
188
+		return err
189
+	}
190
+	if parent.id == e.id {
191
+		return fmt.Errorf("Cannot set self as child")
192
+	}
193
+
194
+	if _, err := tx.Exec("INSERT INTO edge (parent_id, name, entity_id) VALUES (?,?,?);", parent.id, name, e.id); err != nil {
195
+		return err
196
+	}
197
+	return nil
198
+}
199
+
200
+// RootEntity returns the root "/" entity for the database.
201
+func (db *Database) RootEntity() *Entity {
202
+	return &Entity{
203
+		id: "0",
204
+	}
205
+}
206
+
207
+// Get returns the entity for a given path.
208
+func (db *Database) Get(name string) *Entity {
209
+	db.mux.RLock()
210
+	defer db.mux.RUnlock()
211
+
212
+	e, err := db.get(name)
213
+	if err != nil {
214
+		return nil
215
+	}
216
+	return e
217
+}
218
+
219
+func (db *Database) get(name string) (*Entity, error) {
220
+	e := db.RootEntity()
221
+	// We always know the root name so return it if
222
+	// it is requested
223
+	if name == "/" {
224
+		return e, nil
225
+	}
226
+
227
+	parts := split(name)
228
+	for i := 1; i < len(parts); i++ {
229
+		p := parts[i]
230
+		if p == "" {
231
+			continue
232
+		}
233
+
234
+		next := db.child(e, p)
235
+		if next == nil {
236
+			return nil, fmt.Errorf("Cannot find child for %s", name)
237
+		}
238
+		e = next
239
+	}
240
+	return e, nil
241
+
242
+}
243
+
244
+// List all entities by from the name.
245
+// The key will be the full path of the entity.
246
+func (db *Database) List(name string, depth int) Entities {
247
+	db.mux.RLock()
248
+	defer db.mux.RUnlock()
249
+
250
+	out := Entities{}
251
+	e, err := db.get(name)
252
+	if err != nil {
253
+		return out
254
+	}
255
+
256
+	children, err := db.children(e, name, depth, nil)
257
+	if err != nil {
258
+		return out
259
+	}
260
+
261
+	for _, c := range children {
262
+		out[c.FullPath] = c.Entity
263
+	}
264
+	return out
265
+}
266
+
267
+// Walk through the child graph of an entity, calling walkFunc for each child entity.
268
+// It is safe for walkFunc to call graph functions.
269
+func (db *Database) Walk(name string, walkFunc WalkFunc, depth int) error {
270
+	children, err := db.Children(name, depth)
271
+	if err != nil {
272
+		return err
273
+	}
274
+
275
+	// Note: the database lock must not be held while calling walkFunc
276
+	for _, c := range children {
277
+		if err := walkFunc(c.FullPath, c.Entity); err != nil {
278
+			return err
279
+		}
280
+	}
281
+	return nil
282
+}
283
+
284
+// Children returns the children of the specified entity.
285
+func (db *Database) Children(name string, depth int) ([]WalkMeta, error) {
286
+	db.mux.RLock()
287
+	defer db.mux.RUnlock()
288
+
289
+	e, err := db.get(name)
290
+	if err != nil {
291
+		return nil, err
292
+	}
293
+
294
+	return db.children(e, name, depth, nil)
295
+}
296
+
297
+// Parents returns the parents of a specified entity.
298
+func (db *Database) Parents(name string) ([]string, error) {
299
+	db.mux.RLock()
300
+	defer db.mux.RUnlock()
301
+
302
+	e, err := db.get(name)
303
+	if err != nil {
304
+		return nil, err
305
+	}
306
+	return db.parents(e)
307
+}
308
+
309
+// Refs returns the reference count for a specified id.
310
+func (db *Database) Refs(id string) int {
311
+	db.mux.RLock()
312
+	defer db.mux.RUnlock()
313
+
314
+	var count int
315
+	if err := db.conn.QueryRow("SELECT COUNT(*) FROM edge WHERE entity_id = ?;", id).Scan(&count); err != nil {
316
+		return 0
317
+	}
318
+	return count
319
+}
320
+
321
+// RefPaths returns all the id's path references.
322
+func (db *Database) RefPaths(id string) Edges {
323
+	db.mux.RLock()
324
+	defer db.mux.RUnlock()
325
+
326
+	refs := Edges{}
327
+
328
+	rows, err := db.conn.Query("SELECT name, parent_id FROM edge WHERE entity_id = ?;", id)
329
+	if err != nil {
330
+		return refs
331
+	}
332
+	defer rows.Close()
333
+
334
+	for rows.Next() {
335
+		var name string
336
+		var parentID string
337
+		if err := rows.Scan(&name, &parentID); err != nil {
338
+			return refs
339
+		}
340
+		refs = append(refs, &Edge{
341
+			EntityID: id,
342
+			Name:     name,
343
+			ParentID: parentID,
344
+		})
345
+	}
346
+	return refs
347
+}
348
+
349
+// Delete the reference to an entity at a given path.
350
+func (db *Database) Delete(name string) error {
351
+	db.mux.Lock()
352
+	defer db.mux.Unlock()
353
+
354
+	if name == "/" {
355
+		return fmt.Errorf("Cannot delete root entity")
356
+	}
357
+
358
+	parentPath, n := splitPath(name)
359
+	parent, err := db.get(parentPath)
360
+	if err != nil {
361
+		return err
362
+	}
363
+
364
+	if _, err := db.conn.Exec("DELETE FROM edge WHERE parent_id = ? AND name = ?;", parent.id, n); err != nil {
365
+		return err
366
+	}
367
+	return nil
368
+}
369
+
370
+// Purge removes the entity with the specified id
371
+// Walk the graph to make sure all references to the entity
372
+// are removed and return the number of references removed
373
+func (db *Database) Purge(id string) (int, error) {
374
+	db.mux.Lock()
375
+	defer db.mux.Unlock()
376
+
377
+	tx, err := db.conn.Begin()
378
+	if err != nil {
379
+		return -1, err
380
+	}
381
+
382
+	// Delete all edges
383
+	rows, err := tx.Exec("DELETE FROM edge WHERE entity_id = ?;", id)
384
+	if err != nil {
385
+		tx.Rollback()
386
+		return -1, err
387
+	}
388
+	changes, err := rows.RowsAffected()
389
+	if err != nil {
390
+		return -1, err
391
+	}
392
+
393
+	// Clear who's using this id as parent
394
+	refs, err := tx.Exec("DELETE FROM edge WHERE parent_id = ?;", id)
395
+	if err != nil {
396
+		tx.Rollback()
397
+		return -1, err
398
+	}
399
+	refsCount, err := refs.RowsAffected()
400
+	if err != nil {
401
+		return -1, err
402
+	}
403
+
404
+	// Delete entity
405
+	if _, err := tx.Exec("DELETE FROM entity where id = ?;", id); err != nil {
406
+		tx.Rollback()
407
+		return -1, err
408
+	}
409
+
410
+	if err := tx.Commit(); err != nil {
411
+		return -1, err
412
+	}
413
+
414
+	return int(changes + refsCount), nil
415
+}
416
+
417
+// Rename an edge for a given path
418
+func (db *Database) Rename(currentName, newName string) error {
419
+	db.mux.Lock()
420
+	defer db.mux.Unlock()
421
+
422
+	parentPath, name := splitPath(currentName)
423
+	newParentPath, newEdgeName := splitPath(newName)
424
+
425
+	if parentPath != newParentPath {
426
+		return fmt.Errorf("Cannot rename when root paths do not match %s != %s", parentPath, newParentPath)
427
+	}
428
+
429
+	parent, err := db.get(parentPath)
430
+	if err != nil {
431
+		return err
432
+	}
433
+
434
+	rows, err := db.conn.Exec("UPDATE edge SET name = ? WHERE parent_id = ? AND name = ?;", newEdgeName, parent.id, name)
435
+	if err != nil {
436
+		return err
437
+	}
438
+	i, err := rows.RowsAffected()
439
+	if err != nil {
440
+		return err
441
+	}
442
+	if i == 0 {
443
+		return fmt.Errorf("Cannot locate edge for %s %s", parent.id, name)
444
+	}
445
+	return nil
446
+}
447
+
448
+// WalkMeta stores the walk metadata.
449
+type WalkMeta struct {
450
+	Parent   *Entity
451
+	Entity   *Entity
452
+	FullPath string
453
+	Edge     *Edge
454
+}
455
+
456
+func (db *Database) children(e *Entity, name string, depth int, entities []WalkMeta) ([]WalkMeta, error) {
457
+	if e == nil {
458
+		return entities, nil
459
+	}
460
+
461
+	rows, err := db.conn.Query("SELECT entity_id, name FROM edge where parent_id = ?;", e.id)
462
+	if err != nil {
463
+		return nil, err
464
+	}
465
+	defer rows.Close()
466
+
467
+	for rows.Next() {
468
+		var entityID, entityName string
469
+		if err := rows.Scan(&entityID, &entityName); err != nil {
470
+			return nil, err
471
+		}
472
+		child := &Entity{entityID}
473
+		edge := &Edge{
474
+			ParentID: e.id,
475
+			Name:     entityName,
476
+			EntityID: child.id,
477
+		}
478
+
479
+		meta := WalkMeta{
480
+			Parent:   e,
481
+			Entity:   child,
482
+			FullPath: path.Join(name, edge.Name),
483
+			Edge:     edge,
484
+		}
485
+
486
+		entities = append(entities, meta)
487
+
488
+		if depth != 0 {
489
+			nDepth := depth
490
+			if depth != -1 {
491
+				nDepth--
492
+			}
493
+			entities, err = db.children(child, meta.FullPath, nDepth, entities)
494
+			if err != nil {
495
+				return nil, err
496
+			}
497
+		}
498
+	}
499
+
500
+	return entities, nil
501
+}
502
+
503
+func (db *Database) parents(e *Entity) (parents []string, err error) {
504
+	if e == nil {
505
+		return parents, nil
506
+	}
507
+
508
+	rows, err := db.conn.Query("SELECT parent_id FROM edge where entity_id = ?;", e.id)
509
+	if err != nil {
510
+		return nil, err
511
+	}
512
+	defer rows.Close()
513
+
514
+	for rows.Next() {
515
+		var parentID string
516
+		if err := rows.Scan(&parentID); err != nil {
517
+			return nil, err
518
+		}
519
+		parents = append(parents, parentID)
520
+	}
521
+
522
+	return parents, nil
523
+}
524
+
525
+// Return the entity based on the parent path and name.
526
+func (db *Database) child(parent *Entity, name string) *Entity {
527
+	var id string
528
+	if err := db.conn.QueryRow("SELECT entity_id FROM edge WHERE parent_id = ? AND name = ?;", parent.id, name).Scan(&id); err != nil {
529
+		return nil
530
+	}
531
+	return &Entity{id}
532
+}
533
+
534
+// ID returns the id used to reference this entity.
535
+func (e *Entity) ID() string {
536
+	return e.id
537
+}
538
+
539
+// Paths returns the paths sorted by depth.
540
+func (e Entities) Paths() []string {
541
+	out := make([]string, len(e))
542
+	var i int
543
+	for k := range e {
544
+		out[i] = k
545
+		i++
546
+	}
547
+	sortByDepth(out)
548
+
549
+	return out
550
+}
0 551
new file mode 100644
... ...
@@ -0,0 +1,721 @@
0
+package graphdb
1
+
2
+import (
3
+	"database/sql"
4
+	"fmt"
5
+	"os"
6
+	"path"
7
+	"runtime"
8
+	"strconv"
9
+	"testing"
10
+
11
+	_ "github.com/mattn/go-sqlite3"
12
+)
13
+
14
+func newTestDb(t *testing.T) (*Database, string) {
15
+	p := path.Join(os.TempDir(), "sqlite.db")
16
+	conn, err := sql.Open("sqlite3", p)
17
+	db, err := NewDatabase(conn)
18
+	if err != nil {
19
+		t.Fatal(err)
20
+	}
21
+	return db, p
22
+}
23
+
24
+func destroyTestDb(dbPath string) {
25
+	os.Remove(dbPath)
26
+}
27
+
28
+func TestNewDatabase(t *testing.T) {
29
+	db, dbpath := newTestDb(t)
30
+	if db == nil {
31
+		t.Fatal("Database should not be nil")
32
+	}
33
+	db.Close()
34
+	defer destroyTestDb(dbpath)
35
+}
36
+
37
+func TestCreateRootEntity(t *testing.T) {
38
+	db, dbpath := newTestDb(t)
39
+	defer destroyTestDb(dbpath)
40
+	root := db.RootEntity()
41
+	if root == nil {
42
+		t.Fatal("Root entity should not be nil")
43
+	}
44
+}
45
+
46
+func TestGetRootEntity(t *testing.T) {
47
+	db, dbpath := newTestDb(t)
48
+	defer destroyTestDb(dbpath)
49
+
50
+	e := db.Get("/")
51
+	if e == nil {
52
+		t.Fatal("Entity should not be nil")
53
+	}
54
+	if e.ID() != "0" {
55
+		t.Fatalf("Entity id should be 0, got %s", e.ID())
56
+	}
57
+}
58
+
59
+func TestSetEntityWithDifferentName(t *testing.T) {
60
+	db, dbpath := newTestDb(t)
61
+	defer destroyTestDb(dbpath)
62
+
63
+	db.Set("/test", "1")
64
+	if _, err := db.Set("/other", "1"); err != nil {
65
+		t.Fatal(err)
66
+	}
67
+}
68
+
69
+func TestSetDuplicateEntity(t *testing.T) {
70
+	db, dbpath := newTestDb(t)
71
+	defer destroyTestDb(dbpath)
72
+
73
+	if _, err := db.Set("/foo", "42"); err != nil {
74
+		t.Fatal(err)
75
+	}
76
+	if _, err := db.Set("/foo", "43"); err == nil {
77
+		t.Fatalf("Creating an entry with a duplicate path did not cause an error")
78
+	}
79
+}
80
+
81
+func TestCreateChild(t *testing.T) {
82
+	db, dbpath := newTestDb(t)
83
+	defer destroyTestDb(dbpath)
84
+
85
+	child, err := db.Set("/db", "1")
86
+	if err != nil {
87
+		t.Fatal(err)
88
+	}
89
+	if child == nil {
90
+		t.Fatal("Child should not be nil")
91
+	}
92
+	if child.ID() != "1" {
93
+		t.Fail()
94
+	}
95
+}
96
+
97
+func TestParents(t *testing.T) {
98
+	db, dbpath := newTestDb(t)
99
+	defer destroyTestDb(dbpath)
100
+
101
+	for i := 1; i < 6; i++ {
102
+		a := strconv.Itoa(i)
103
+		if _, err := db.Set("/"+a, a); err != nil {
104
+			t.Fatal(err)
105
+		}
106
+	}
107
+
108
+	for i := 6; i < 11; i++ {
109
+		a := strconv.Itoa(i)
110
+		p := strconv.Itoa(i - 5)
111
+
112
+		key := fmt.Sprintf("/%s/%s", p, a)
113
+
114
+		if _, err := db.Set(key, a); err != nil {
115
+			t.Fatal(err)
116
+		}
117
+
118
+		parents, err := db.Parents(key)
119
+		if err != nil {
120
+			t.Fatal(err)
121
+		}
122
+
123
+		if len(parents) != 1 {
124
+			t.Fatalf("Expected 1 entry for %s got %d", key, len(parents))
125
+		}
126
+
127
+		if parents[0] != p {
128
+			t.Fatalf("ID %s received, %s expected", parents[0], p)
129
+		}
130
+	}
131
+}
132
+
133
+func TestChildren(t *testing.T) {
134
+	// TODO Windows: Port this test
135
+	if runtime.GOOS == "windows" {
136
+		t.Skip("Needs porting to Windows")
137
+	}
138
+	db, dbpath := newTestDb(t)
139
+	defer destroyTestDb(dbpath)
140
+
141
+	str := "/"
142
+	for i := 1; i < 6; i++ {
143
+		a := strconv.Itoa(i)
144
+		if _, err := db.Set(str+a, a); err != nil {
145
+			t.Fatal(err)
146
+		}
147
+
148
+		str = str + a + "/"
149
+	}
150
+
151
+	str = "/"
152
+	for i := 10; i < 30; i++ { // 20 entities
153
+		a := strconv.Itoa(i)
154
+		if _, err := db.Set(str+a, a); err != nil {
155
+			t.Fatal(err)
156
+		}
157
+
158
+		str = str + a + "/"
159
+	}
160
+	entries, err := db.Children("/", 5)
161
+	if err != nil {
162
+		t.Fatal(err)
163
+	}
164
+
165
+	if len(entries) != 11 {
166
+		t.Fatalf("Expect 11 entries for / got %d", len(entries))
167
+	}
168
+
169
+	entries, err = db.Children("/", 20)
170
+	if err != nil {
171
+		t.Fatal(err)
172
+	}
173
+
174
+	if len(entries) != 25 {
175
+		t.Fatalf("Expect 25 entries for / got %d", len(entries))
176
+	}
177
+}
178
+
179
+func TestListAllRootChildren(t *testing.T) {
180
+	// TODO Windows: Port this test
181
+	if runtime.GOOS == "windows" {
182
+		t.Skip("Needs porting to Windows")
183
+	}
184
+
185
+	db, dbpath := newTestDb(t)
186
+	defer destroyTestDb(dbpath)
187
+
188
+	for i := 1; i < 6; i++ {
189
+		a := strconv.Itoa(i)
190
+		if _, err := db.Set("/"+a, a); err != nil {
191
+			t.Fatal(err)
192
+		}
193
+	}
194
+	entries := db.List("/", -1)
195
+	if len(entries) != 5 {
196
+		t.Fatalf("Expect 5 entries for / got %d", len(entries))
197
+	}
198
+}
199
+
200
+func TestListAllSubChildren(t *testing.T) {
201
+	// TODO Windows: Port this test
202
+	if runtime.GOOS == "windows" {
203
+		t.Skip("Needs porting to Windows")
204
+	}
205
+	db, dbpath := newTestDb(t)
206
+	defer destroyTestDb(dbpath)
207
+
208
+	_, err := db.Set("/webapp", "1")
209
+	if err != nil {
210
+		t.Fatal(err)
211
+	}
212
+	child2, err := db.Set("/db", "2")
213
+	if err != nil {
214
+		t.Fatal(err)
215
+	}
216
+	child4, err := db.Set("/logs", "4")
217
+	if err != nil {
218
+		t.Fatal(err)
219
+	}
220
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
221
+		t.Fatal(err)
222
+	}
223
+
224
+	child3, err := db.Set("/sentry", "3")
225
+	if err != nil {
226
+		t.Fatal(err)
227
+	}
228
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
229
+		t.Fatal(err)
230
+	}
231
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
232
+		t.Fatal(err)
233
+	}
234
+
235
+	entries := db.List("/webapp", 1)
236
+	if len(entries) != 3 {
237
+		t.Fatalf("Expect 3 entries for / got %d", len(entries))
238
+	}
239
+
240
+	entries = db.List("/webapp", 0)
241
+	if len(entries) != 2 {
242
+		t.Fatalf("Expect 2 entries for / got %d", len(entries))
243
+	}
244
+}
245
+
246
+func TestAddSelfAsChild(t *testing.T) {
247
+	// TODO Windows: Port this test
248
+	if runtime.GOOS == "windows" {
249
+		t.Skip("Needs porting to Windows")
250
+	}
251
+	db, dbpath := newTestDb(t)
252
+	defer destroyTestDb(dbpath)
253
+
254
+	child, err := db.Set("/test", "1")
255
+	if err != nil {
256
+		t.Fatal(err)
257
+	}
258
+	if _, err := db.Set("/test/other", child.ID()); err == nil {
259
+		t.Fatal("Error should not be nil")
260
+	}
261
+}
262
+
263
+func TestAddChildToNonExistentRoot(t *testing.T) {
264
+	db, dbpath := newTestDb(t)
265
+	defer destroyTestDb(dbpath)
266
+
267
+	if _, err := db.Set("/myapp", "1"); err != nil {
268
+		t.Fatal(err)
269
+	}
270
+
271
+	if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
272
+		t.Fatal("Error should not be nil")
273
+	}
274
+}
275
+
276
+func TestWalkAll(t *testing.T) {
277
+	// TODO Windows: Port this test
278
+	if runtime.GOOS == "windows" {
279
+		t.Skip("Needs porting to Windows")
280
+	}
281
+	db, dbpath := newTestDb(t)
282
+	defer destroyTestDb(dbpath)
283
+	_, err := db.Set("/webapp", "1")
284
+	if err != nil {
285
+		t.Fatal(err)
286
+	}
287
+	child2, err := db.Set("/db", "2")
288
+	if err != nil {
289
+		t.Fatal(err)
290
+	}
291
+	child4, err := db.Set("/db/logs", "4")
292
+	if err != nil {
293
+		t.Fatal(err)
294
+	}
295
+	if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
296
+		t.Fatal(err)
297
+	}
298
+
299
+	child3, err := db.Set("/sentry", "3")
300
+	if err != nil {
301
+		t.Fatal(err)
302
+	}
303
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
304
+		t.Fatal(err)
305
+	}
306
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
307
+		t.Fatal(err)
308
+	}
309
+
310
+	child5, err := db.Set("/gograph", "5")
311
+	if err != nil {
312
+		t.Fatal(err)
313
+	}
314
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
315
+		t.Fatal(err)
316
+	}
317
+
318
+	if err := db.Walk("/", func(p string, e *Entity) error {
319
+		t.Logf("Path: %s Entity: %s", p, e.ID())
320
+		return nil
321
+	}, -1); err != nil {
322
+		t.Fatal(err)
323
+	}
324
+}
325
+
326
+func TestGetEntityByPath(t *testing.T) {
327
+	// TODO Windows: Port this test
328
+	if runtime.GOOS == "windows" {
329
+		t.Skip("Needs porting to Windows")
330
+	}
331
+	db, dbpath := newTestDb(t)
332
+	defer destroyTestDb(dbpath)
333
+	_, err := db.Set("/webapp", "1")
334
+	if err != nil {
335
+		t.Fatal(err)
336
+	}
337
+	child2, err := db.Set("/db", "2")
338
+	if err != nil {
339
+		t.Fatal(err)
340
+	}
341
+	child4, err := db.Set("/logs", "4")
342
+	if err != nil {
343
+		t.Fatal(err)
344
+	}
345
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
346
+		t.Fatal(err)
347
+	}
348
+
349
+	child3, err := db.Set("/sentry", "3")
350
+	if err != nil {
351
+		t.Fatal(err)
352
+	}
353
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
354
+		t.Fatal(err)
355
+	}
356
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
357
+		t.Fatal(err)
358
+	}
359
+
360
+	child5, err := db.Set("/gograph", "5")
361
+	if err != nil {
362
+		t.Fatal(err)
363
+	}
364
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
365
+		t.Fatal(err)
366
+	}
367
+
368
+	entity := db.Get("/webapp/db/logs")
369
+	if entity == nil {
370
+		t.Fatal("Entity should not be nil")
371
+	}
372
+	if entity.ID() != "4" {
373
+		t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
374
+	}
375
+}
376
+
377
+func TestEnitiesPaths(t *testing.T) {
378
+	// TODO Windows: Port this test
379
+	if runtime.GOOS == "windows" {
380
+		t.Skip("Needs porting to Windows")
381
+	}
382
+	db, dbpath := newTestDb(t)
383
+	defer destroyTestDb(dbpath)
384
+	_, err := db.Set("/webapp", "1")
385
+	if err != nil {
386
+		t.Fatal(err)
387
+	}
388
+	child2, err := db.Set("/db", "2")
389
+	if err != nil {
390
+		t.Fatal(err)
391
+	}
392
+	child4, err := db.Set("/logs", "4")
393
+	if err != nil {
394
+		t.Fatal(err)
395
+	}
396
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
397
+		t.Fatal(err)
398
+	}
399
+
400
+	child3, err := db.Set("/sentry", "3")
401
+	if err != nil {
402
+		t.Fatal(err)
403
+	}
404
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
405
+		t.Fatal(err)
406
+	}
407
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
408
+		t.Fatal(err)
409
+	}
410
+
411
+	child5, err := db.Set("/gograph", "5")
412
+	if err != nil {
413
+		t.Fatal(err)
414
+	}
415
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
416
+		t.Fatal(err)
417
+	}
418
+
419
+	out := db.List("/", -1)
420
+	for _, p := range out.Paths() {
421
+		t.Log(p)
422
+	}
423
+}
424
+
425
+func TestDeleteRootEntity(t *testing.T) {
426
+	db, dbpath := newTestDb(t)
427
+	defer destroyTestDb(dbpath)
428
+
429
+	if err := db.Delete("/"); err == nil {
430
+		t.Fatal("Error should not be nil")
431
+	}
432
+}
433
+
434
+func TestDeleteEntity(t *testing.T) {
435
+	// TODO Windows: Port this test
436
+	if runtime.GOOS == "windows" {
437
+		t.Skip("Needs porting to Windows")
438
+	}
439
+	db, dbpath := newTestDb(t)
440
+	defer destroyTestDb(dbpath)
441
+	_, err := db.Set("/webapp", "1")
442
+	if err != nil {
443
+		t.Fatal(err)
444
+	}
445
+	child2, err := db.Set("/db", "2")
446
+	if err != nil {
447
+		t.Fatal(err)
448
+	}
449
+	child4, err := db.Set("/logs", "4")
450
+	if err != nil {
451
+		t.Fatal(err)
452
+	}
453
+	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
454
+		t.Fatal(err)
455
+	}
456
+
457
+	child3, err := db.Set("/sentry", "3")
458
+	if err != nil {
459
+		t.Fatal(err)
460
+	}
461
+	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
462
+		t.Fatal(err)
463
+	}
464
+	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
465
+		t.Fatal(err)
466
+	}
467
+
468
+	child5, err := db.Set("/gograph", "5")
469
+	if err != nil {
470
+		t.Fatal(err)
471
+	}
472
+	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
473
+		t.Fatal(err)
474
+	}
475
+
476
+	if err := db.Delete("/webapp/sentry"); err != nil {
477
+		t.Fatal(err)
478
+	}
479
+	entity := db.Get("/webapp/sentry")
480
+	if entity != nil {
481
+		t.Fatal("Entity /webapp/sentry should be nil")
482
+	}
483
+}
484
+
485
+func TestCountRefs(t *testing.T) {
486
+	// TODO Windows: Port this test
487
+	if runtime.GOOS == "windows" {
488
+		t.Skip("Needs porting to Windows")
489
+	}
490
+	db, dbpath := newTestDb(t)
491
+	defer destroyTestDb(dbpath)
492
+
493
+	db.Set("/webapp", "1")
494
+
495
+	if db.Refs("1") != 1 {
496
+		t.Fatal("Expect reference count to be 1")
497
+	}
498
+
499
+	db.Set("/db", "2")
500
+	db.Set("/webapp/db", "2")
501
+	if db.Refs("2") != 2 {
502
+		t.Fatal("Expect reference count to be 2")
503
+	}
504
+}
505
+
506
+func TestPurgeId(t *testing.T) {
507
+	// TODO Windows: Port this test
508
+	if runtime.GOOS == "windows" {
509
+		t.Skip("Needs porting to Windows")
510
+	}
511
+
512
+	db, dbpath := newTestDb(t)
513
+	defer destroyTestDb(dbpath)
514
+
515
+	db.Set("/webapp", "1")
516
+
517
+	if c := db.Refs("1"); c != 1 {
518
+		t.Fatalf("Expect reference count to be 1, got %d", c)
519
+	}
520
+
521
+	db.Set("/db", "2")
522
+	db.Set("/webapp/db", "2")
523
+
524
+	count, err := db.Purge("2")
525
+	if err != nil {
526
+		t.Fatal(err)
527
+	}
528
+	if count != 2 {
529
+		t.Fatalf("Expected 2 references to be removed, got %d", count)
530
+	}
531
+}
532
+
533
+// Regression test https://github.com/docker/docker/issues/12334
534
+func TestPurgeIdRefPaths(t *testing.T) {
535
+	// TODO Windows: Port this test
536
+	if runtime.GOOS == "windows" {
537
+		t.Skip("Needs porting to Windows")
538
+	}
539
+	db, dbpath := newTestDb(t)
540
+	defer destroyTestDb(dbpath)
541
+
542
+	db.Set("/webapp", "1")
543
+	db.Set("/db", "2")
544
+
545
+	db.Set("/db/webapp", "1")
546
+
547
+	if c := db.Refs("1"); c != 2 {
548
+		t.Fatalf("Expected 2 reference for webapp, got %d", c)
549
+	}
550
+	if c := db.Refs("2"); c != 1 {
551
+		t.Fatalf("Expected 1 reference for db, got %d", c)
552
+	}
553
+
554
+	if rp := db.RefPaths("2"); len(rp) != 1 {
555
+		t.Fatalf("Expected 1 reference path for db, got %d", len(rp))
556
+	}
557
+
558
+	count, err := db.Purge("2")
559
+	if err != nil {
560
+		t.Fatal(err)
561
+	}
562
+
563
+	if count != 2 {
564
+		t.Fatalf("Expected 2 rows to be removed, got %d", count)
565
+	}
566
+
567
+	if c := db.Refs("2"); c != 0 {
568
+		t.Fatalf("Expected 0 reference for db, got %d", c)
569
+	}
570
+	if c := db.Refs("1"); c != 1 {
571
+		t.Fatalf("Expected 1 reference for webapp, got %d", c)
572
+	}
573
+}
574
+
575
+func TestRename(t *testing.T) {
576
+	// TODO Windows: Port this test
577
+	if runtime.GOOS == "windows" {
578
+		t.Skip("Needs porting to Windows")
579
+	}
580
+	db, dbpath := newTestDb(t)
581
+	defer destroyTestDb(dbpath)
582
+
583
+	db.Set("/webapp", "1")
584
+
585
+	if db.Refs("1") != 1 {
586
+		t.Fatal("Expect reference count to be 1")
587
+	}
588
+
589
+	db.Set("/db", "2")
590
+	db.Set("/webapp/db", "2")
591
+
592
+	if db.Get("/webapp/db") == nil {
593
+		t.Fatal("Cannot find entity at path /webapp/db")
594
+	}
595
+
596
+	if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
597
+		t.Fatal(err)
598
+	}
599
+	if db.Get("/webapp/db") != nil {
600
+		t.Fatal("Entity should not exist at /webapp/db")
601
+	}
602
+	if db.Get("/webapp/newdb") == nil {
603
+		t.Fatal("Cannot find entity at path /webapp/newdb")
604
+	}
605
+
606
+}
607
+
608
+func TestCreateMultipleNames(t *testing.T) {
609
+	// TODO Windows: Port this test
610
+	if runtime.GOOS == "windows" {
611
+		t.Skip("Needs porting to Windows")
612
+	}
613
+
614
+	db, dbpath := newTestDb(t)
615
+	defer destroyTestDb(dbpath)
616
+
617
+	db.Set("/db", "1")
618
+	if _, err := db.Set("/myapp", "1"); err != nil {
619
+		t.Fatal(err)
620
+	}
621
+
622
+	db.Walk("/", func(p string, e *Entity) error {
623
+		t.Logf("%s\n", p)
624
+		return nil
625
+	}, -1)
626
+}
627
+
628
+func TestRefPaths(t *testing.T) {
629
+	db, dbpath := newTestDb(t)
630
+	defer destroyTestDb(dbpath)
631
+
632
+	db.Set("/webapp", "1")
633
+
634
+	db.Set("/db", "2")
635
+	db.Set("/webapp/db", "2")
636
+
637
+	refs := db.RefPaths("2")
638
+	if len(refs) != 2 {
639
+		t.Fatalf("Expected reference count to be 2, got %d", len(refs))
640
+	}
641
+}
642
+
643
+func TestExistsTrue(t *testing.T) {
644
+	db, dbpath := newTestDb(t)
645
+	defer destroyTestDb(dbpath)
646
+
647
+	db.Set("/testing", "1")
648
+
649
+	if !db.Exists("/testing") {
650
+		t.Fatalf("/tesing should exist")
651
+	}
652
+}
653
+
654
+func TestExistsFalse(t *testing.T) {
655
+	// TODO Windows: Port this test
656
+	if runtime.GOOS == "windows" {
657
+		t.Skip("Needs porting to Windows")
658
+	}
659
+	db, dbpath := newTestDb(t)
660
+	defer destroyTestDb(dbpath)
661
+
662
+	db.Set("/toerhe", "1")
663
+
664
+	if db.Exists("/testing") {
665
+		t.Fatalf("/tesing should not exist")
666
+	}
667
+
668
+}
669
+
670
+func TestGetNameWithTrailingSlash(t *testing.T) {
671
+	db, dbpath := newTestDb(t)
672
+	defer destroyTestDb(dbpath)
673
+
674
+	db.Set("/todo", "1")
675
+
676
+	e := db.Get("/todo/")
677
+	if e == nil {
678
+		t.Fatalf("Entity should not be nil")
679
+	}
680
+}
681
+
682
+func TestConcurrentWrites(t *testing.T) {
683
+	// TODO Windows: Port this test
684
+	if runtime.GOOS == "windows" {
685
+		t.Skip("Needs porting to Windows")
686
+	}
687
+	db, dbpath := newTestDb(t)
688
+	defer destroyTestDb(dbpath)
689
+
690
+	errs := make(chan error, 2)
691
+
692
+	save := func(name string, id string) {
693
+		if _, err := db.Set(fmt.Sprintf("/%s", name), id); err != nil {
694
+			errs <- err
695
+		}
696
+		errs <- nil
697
+	}
698
+	purge := func(id string) {
699
+		if _, err := db.Purge(id); err != nil {
700
+			errs <- err
701
+		}
702
+		errs <- nil
703
+	}
704
+
705
+	save("/1", "1")
706
+
707
+	go purge("1")
708
+	go save("/2", "2")
709
+
710
+	any := false
711
+	for i := 0; i < 2; i++ {
712
+		if err := <-errs; err != nil {
713
+			any = true
714
+			t.Log(err)
715
+		}
716
+	}
717
+	if any {
718
+		t.Fail()
719
+	}
720
+}
0 721
deleted file mode 100644
... ...
@@ -1,721 +0,0 @@
1
-package graphdb
2
-
3
-import (
4
-	"database/sql"
5
-	"fmt"
6
-	"os"
7
-	"path"
8
-	"runtime"
9
-	"strconv"
10
-	"testing"
11
-
12
-	_ "github.com/mattn/go-sqlite3"
13
-)
14
-
15
-func newTestDb(t *testing.T) (*Database, string) {
16
-	p := path.Join(os.TempDir(), "sqlite.db")
17
-	conn, err := sql.Open("sqlite3", p)
18
-	db, err := NewDatabase(conn)
19
-	if err != nil {
20
-		t.Fatal(err)
21
-	}
22
-	return db, p
23
-}
24
-
25
-func destroyTestDb(dbPath string) {
26
-	os.Remove(dbPath)
27
-}
28
-
29
-func TestNewDatabase(t *testing.T) {
30
-	db, dbpath := newTestDb(t)
31
-	if db == nil {
32
-		t.Fatal("Database should not be nil")
33
-	}
34
-	db.Close()
35
-	defer destroyTestDb(dbpath)
36
-}
37
-
38
-func TestCreateRootEntity(t *testing.T) {
39
-	db, dbpath := newTestDb(t)
40
-	defer destroyTestDb(dbpath)
41
-	root := db.RootEntity()
42
-	if root == nil {
43
-		t.Fatal("Root entity should not be nil")
44
-	}
45
-}
46
-
47
-func TestGetRootEntity(t *testing.T) {
48
-	db, dbpath := newTestDb(t)
49
-	defer destroyTestDb(dbpath)
50
-
51
-	e := db.Get("/")
52
-	if e == nil {
53
-		t.Fatal("Entity should not be nil")
54
-	}
55
-	if e.ID() != "0" {
56
-		t.Fatalf("Entity id should be 0, got %s", e.ID())
57
-	}
58
-}
59
-
60
-func TestSetEntityWithDifferentName(t *testing.T) {
61
-	db, dbpath := newTestDb(t)
62
-	defer destroyTestDb(dbpath)
63
-
64
-	db.Set("/test", "1")
65
-	if _, err := db.Set("/other", "1"); err != nil {
66
-		t.Fatal(err)
67
-	}
68
-}
69
-
70
-func TestSetDuplicateEntity(t *testing.T) {
71
-	db, dbpath := newTestDb(t)
72
-	defer destroyTestDb(dbpath)
73
-
74
-	if _, err := db.Set("/foo", "42"); err != nil {
75
-		t.Fatal(err)
76
-	}
77
-	if _, err := db.Set("/foo", "43"); err == nil {
78
-		t.Fatalf("Creating an entry with a duplicate path did not cause an error")
79
-	}
80
-}
81
-
82
-func TestCreateChild(t *testing.T) {
83
-	db, dbpath := newTestDb(t)
84
-	defer destroyTestDb(dbpath)
85
-
86
-	child, err := db.Set("/db", "1")
87
-	if err != nil {
88
-		t.Fatal(err)
89
-	}
90
-	if child == nil {
91
-		t.Fatal("Child should not be nil")
92
-	}
93
-	if child.ID() != "1" {
94
-		t.Fail()
95
-	}
96
-}
97
-
98
-func TestParents(t *testing.T) {
99
-	db, dbpath := newTestDb(t)
100
-	defer destroyTestDb(dbpath)
101
-
102
-	for i := 1; i < 6; i++ {
103
-		a := strconv.Itoa(i)
104
-		if _, err := db.Set("/"+a, a); err != nil {
105
-			t.Fatal(err)
106
-		}
107
-	}
108
-
109
-	for i := 6; i < 11; i++ {
110
-		a := strconv.Itoa(i)
111
-		p := strconv.Itoa(i - 5)
112
-
113
-		key := fmt.Sprintf("/%s/%s", p, a)
114
-
115
-		if _, err := db.Set(key, a); err != nil {
116
-			t.Fatal(err)
117
-		}
118
-
119
-		parents, err := db.Parents(key)
120
-		if err != nil {
121
-			t.Fatal(err)
122
-		}
123
-
124
-		if len(parents) != 1 {
125
-			t.Fatalf("Expected 1 entry for %s got %d", key, len(parents))
126
-		}
127
-
128
-		if parents[0] != p {
129
-			t.Fatalf("ID %s received, %s expected", parents[0], p)
130
-		}
131
-	}
132
-}
133
-
134
-func TestChildren(t *testing.T) {
135
-	// TODO Windows: Port this test
136
-	if runtime.GOOS == "windows" {
137
-		t.Skip("Needs porting to Windows")
138
-	}
139
-	db, dbpath := newTestDb(t)
140
-	defer destroyTestDb(dbpath)
141
-
142
-	str := "/"
143
-	for i := 1; i < 6; i++ {
144
-		a := strconv.Itoa(i)
145
-		if _, err := db.Set(str+a, a); err != nil {
146
-			t.Fatal(err)
147
-		}
148
-
149
-		str = str + a + "/"
150
-	}
151
-
152
-	str = "/"
153
-	for i := 10; i < 30; i++ { // 20 entities
154
-		a := strconv.Itoa(i)
155
-		if _, err := db.Set(str+a, a); err != nil {
156
-			t.Fatal(err)
157
-		}
158
-
159
-		str = str + a + "/"
160
-	}
161
-	entries, err := db.Children("/", 5)
162
-	if err != nil {
163
-		t.Fatal(err)
164
-	}
165
-
166
-	if len(entries) != 11 {
167
-		t.Fatalf("Expect 11 entries for / got %d", len(entries))
168
-	}
169
-
170
-	entries, err = db.Children("/", 20)
171
-	if err != nil {
172
-		t.Fatal(err)
173
-	}
174
-
175
-	if len(entries) != 25 {
176
-		t.Fatalf("Expect 25 entries for / got %d", len(entries))
177
-	}
178
-}
179
-
180
-func TestListAllRootChildren(t *testing.T) {
181
-	// TODO Windows: Port this test
182
-	if runtime.GOOS == "windows" {
183
-		t.Skip("Needs porting to Windows")
184
-	}
185
-
186
-	db, dbpath := newTestDb(t)
187
-	defer destroyTestDb(dbpath)
188
-
189
-	for i := 1; i < 6; i++ {
190
-		a := strconv.Itoa(i)
191
-		if _, err := db.Set("/"+a, a); err != nil {
192
-			t.Fatal(err)
193
-		}
194
-	}
195
-	entries := db.List("/", -1)
196
-	if len(entries) != 5 {
197
-		t.Fatalf("Expect 5 entries for / got %d", len(entries))
198
-	}
199
-}
200
-
201
-func TestListAllSubChildren(t *testing.T) {
202
-	// TODO Windows: Port this test
203
-	if runtime.GOOS == "windows" {
204
-		t.Skip("Needs porting to Windows")
205
-	}
206
-	db, dbpath := newTestDb(t)
207
-	defer destroyTestDb(dbpath)
208
-
209
-	_, err := db.Set("/webapp", "1")
210
-	if err != nil {
211
-		t.Fatal(err)
212
-	}
213
-	child2, err := db.Set("/db", "2")
214
-	if err != nil {
215
-		t.Fatal(err)
216
-	}
217
-	child4, err := db.Set("/logs", "4")
218
-	if err != nil {
219
-		t.Fatal(err)
220
-	}
221
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
222
-		t.Fatal(err)
223
-	}
224
-
225
-	child3, err := db.Set("/sentry", "3")
226
-	if err != nil {
227
-		t.Fatal(err)
228
-	}
229
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
230
-		t.Fatal(err)
231
-	}
232
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
233
-		t.Fatal(err)
234
-	}
235
-
236
-	entries := db.List("/webapp", 1)
237
-	if len(entries) != 3 {
238
-		t.Fatalf("Expect 3 entries for / got %d", len(entries))
239
-	}
240
-
241
-	entries = db.List("/webapp", 0)
242
-	if len(entries) != 2 {
243
-		t.Fatalf("Expect 2 entries for / got %d", len(entries))
244
-	}
245
-}
246
-
247
-func TestAddSelfAsChild(t *testing.T) {
248
-	// TODO Windows: Port this test
249
-	if runtime.GOOS == "windows" {
250
-		t.Skip("Needs porting to Windows")
251
-	}
252
-	db, dbpath := newTestDb(t)
253
-	defer destroyTestDb(dbpath)
254
-
255
-	child, err := db.Set("/test", "1")
256
-	if err != nil {
257
-		t.Fatal(err)
258
-	}
259
-	if _, err := db.Set("/test/other", child.ID()); err == nil {
260
-		t.Fatal("Error should not be nil")
261
-	}
262
-}
263
-
264
-func TestAddChildToNonExistentRoot(t *testing.T) {
265
-	db, dbpath := newTestDb(t)
266
-	defer destroyTestDb(dbpath)
267
-
268
-	if _, err := db.Set("/myapp", "1"); err != nil {
269
-		t.Fatal(err)
270
-	}
271
-
272
-	if _, err := db.Set("/myapp/proxy/db", "2"); err == nil {
273
-		t.Fatal("Error should not be nil")
274
-	}
275
-}
276
-
277
-func TestWalkAll(t *testing.T) {
278
-	// TODO Windows: Port this test
279
-	if runtime.GOOS == "windows" {
280
-		t.Skip("Needs porting to Windows")
281
-	}
282
-	db, dbpath := newTestDb(t)
283
-	defer destroyTestDb(dbpath)
284
-	_, err := db.Set("/webapp", "1")
285
-	if err != nil {
286
-		t.Fatal(err)
287
-	}
288
-	child2, err := db.Set("/db", "2")
289
-	if err != nil {
290
-		t.Fatal(err)
291
-	}
292
-	child4, err := db.Set("/db/logs", "4")
293
-	if err != nil {
294
-		t.Fatal(err)
295
-	}
296
-	if _, err := db.Set("/webapp/logs", child4.ID()); err != nil {
297
-		t.Fatal(err)
298
-	}
299
-
300
-	child3, err := db.Set("/sentry", "3")
301
-	if err != nil {
302
-		t.Fatal(err)
303
-	}
304
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
305
-		t.Fatal(err)
306
-	}
307
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
308
-		t.Fatal(err)
309
-	}
310
-
311
-	child5, err := db.Set("/gograph", "5")
312
-	if err != nil {
313
-		t.Fatal(err)
314
-	}
315
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
316
-		t.Fatal(err)
317
-	}
318
-
319
-	if err := db.Walk("/", func(p string, e *Entity) error {
320
-		t.Logf("Path: %s Entity: %s", p, e.ID())
321
-		return nil
322
-	}, -1); err != nil {
323
-		t.Fatal(err)
324
-	}
325
-}
326
-
327
-func TestGetEntityByPath(t *testing.T) {
328
-	// TODO Windows: Port this test
329
-	if runtime.GOOS == "windows" {
330
-		t.Skip("Needs porting to Windows")
331
-	}
332
-	db, dbpath := newTestDb(t)
333
-	defer destroyTestDb(dbpath)
334
-	_, err := db.Set("/webapp", "1")
335
-	if err != nil {
336
-		t.Fatal(err)
337
-	}
338
-	child2, err := db.Set("/db", "2")
339
-	if err != nil {
340
-		t.Fatal(err)
341
-	}
342
-	child4, err := db.Set("/logs", "4")
343
-	if err != nil {
344
-		t.Fatal(err)
345
-	}
346
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
347
-		t.Fatal(err)
348
-	}
349
-
350
-	child3, err := db.Set("/sentry", "3")
351
-	if err != nil {
352
-		t.Fatal(err)
353
-	}
354
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
355
-		t.Fatal(err)
356
-	}
357
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
358
-		t.Fatal(err)
359
-	}
360
-
361
-	child5, err := db.Set("/gograph", "5")
362
-	if err != nil {
363
-		t.Fatal(err)
364
-	}
365
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
366
-		t.Fatal(err)
367
-	}
368
-
369
-	entity := db.Get("/webapp/db/logs")
370
-	if entity == nil {
371
-		t.Fatal("Entity should not be nil")
372
-	}
373
-	if entity.ID() != "4" {
374
-		t.Fatalf("Expected to get entity with id 4, got %s", entity.ID())
375
-	}
376
-}
377
-
378
-func TestEnitiesPaths(t *testing.T) {
379
-	// TODO Windows: Port this test
380
-	if runtime.GOOS == "windows" {
381
-		t.Skip("Needs porting to Windows")
382
-	}
383
-	db, dbpath := newTestDb(t)
384
-	defer destroyTestDb(dbpath)
385
-	_, err := db.Set("/webapp", "1")
386
-	if err != nil {
387
-		t.Fatal(err)
388
-	}
389
-	child2, err := db.Set("/db", "2")
390
-	if err != nil {
391
-		t.Fatal(err)
392
-	}
393
-	child4, err := db.Set("/logs", "4")
394
-	if err != nil {
395
-		t.Fatal(err)
396
-	}
397
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
398
-		t.Fatal(err)
399
-	}
400
-
401
-	child3, err := db.Set("/sentry", "3")
402
-	if err != nil {
403
-		t.Fatal(err)
404
-	}
405
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
406
-		t.Fatal(err)
407
-	}
408
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
409
-		t.Fatal(err)
410
-	}
411
-
412
-	child5, err := db.Set("/gograph", "5")
413
-	if err != nil {
414
-		t.Fatal(err)
415
-	}
416
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
417
-		t.Fatal(err)
418
-	}
419
-
420
-	out := db.List("/", -1)
421
-	for _, p := range out.Paths() {
422
-		t.Log(p)
423
-	}
424
-}
425
-
426
-func TestDeleteRootEntity(t *testing.T) {
427
-	db, dbpath := newTestDb(t)
428
-	defer destroyTestDb(dbpath)
429
-
430
-	if err := db.Delete("/"); err == nil {
431
-		t.Fatal("Error should not be nil")
432
-	}
433
-}
434
-
435
-func TestDeleteEntity(t *testing.T) {
436
-	// TODO Windows: Port this test
437
-	if runtime.GOOS == "windows" {
438
-		t.Skip("Needs porting to Windows")
439
-	}
440
-	db, dbpath := newTestDb(t)
441
-	defer destroyTestDb(dbpath)
442
-	_, err := db.Set("/webapp", "1")
443
-	if err != nil {
444
-		t.Fatal(err)
445
-	}
446
-	child2, err := db.Set("/db", "2")
447
-	if err != nil {
448
-		t.Fatal(err)
449
-	}
450
-	child4, err := db.Set("/logs", "4")
451
-	if err != nil {
452
-		t.Fatal(err)
453
-	}
454
-	if _, err := db.Set("/db/logs", child4.ID()); err != nil {
455
-		t.Fatal(err)
456
-	}
457
-
458
-	child3, err := db.Set("/sentry", "3")
459
-	if err != nil {
460
-		t.Fatal(err)
461
-	}
462
-	if _, err := db.Set("/webapp/sentry", child3.ID()); err != nil {
463
-		t.Fatal(err)
464
-	}
465
-	if _, err := db.Set("/webapp/db", child2.ID()); err != nil {
466
-		t.Fatal(err)
467
-	}
468
-
469
-	child5, err := db.Set("/gograph", "5")
470
-	if err != nil {
471
-		t.Fatal(err)
472
-	}
473
-	if _, err := db.Set("/webapp/same-ref-diff-name", child5.ID()); err != nil {
474
-		t.Fatal(err)
475
-	}
476
-
477
-	if err := db.Delete("/webapp/sentry"); err != nil {
478
-		t.Fatal(err)
479
-	}
480
-	entity := db.Get("/webapp/sentry")
481
-	if entity != nil {
482
-		t.Fatal("Entity /webapp/sentry should be nil")
483
-	}
484
-}
485
-
486
-func TestCountRefs(t *testing.T) {
487
-	// TODO Windows: Port this test
488
-	if runtime.GOOS == "windows" {
489
-		t.Skip("Needs porting to Windows")
490
-	}
491
-	db, dbpath := newTestDb(t)
492
-	defer destroyTestDb(dbpath)
493
-
494
-	db.Set("/webapp", "1")
495
-
496
-	if db.Refs("1") != 1 {
497
-		t.Fatal("Expect reference count to be 1")
498
-	}
499
-
500
-	db.Set("/db", "2")
501
-	db.Set("/webapp/db", "2")
502
-	if db.Refs("2") != 2 {
503
-		t.Fatal("Expect reference count to be 2")
504
-	}
505
-}
506
-
507
-func TestPurgeId(t *testing.T) {
508
-	// TODO Windows: Port this test
509
-	if runtime.GOOS == "windows" {
510
-		t.Skip("Needs porting to Windows")
511
-	}
512
-
513
-	db, dbpath := newTestDb(t)
514
-	defer destroyTestDb(dbpath)
515
-
516
-	db.Set("/webapp", "1")
517
-
518
-	if c := db.Refs("1"); c != 1 {
519
-		t.Fatalf("Expect reference count to be 1, got %d", c)
520
-	}
521
-
522
-	db.Set("/db", "2")
523
-	db.Set("/webapp/db", "2")
524
-
525
-	count, err := db.Purge("2")
526
-	if err != nil {
527
-		t.Fatal(err)
528
-	}
529
-	if count != 2 {
530
-		t.Fatalf("Expected 2 references to be removed, got %d", count)
531
-	}
532
-}
533
-
534
-// Regression test https://github.com/docker/docker/issues/12334
535
-func TestPurgeIdRefPaths(t *testing.T) {
536
-	// TODO Windows: Port this test
537
-	if runtime.GOOS == "windows" {
538
-		t.Skip("Needs porting to Windows")
539
-	}
540
-	db, dbpath := newTestDb(t)
541
-	defer destroyTestDb(dbpath)
542
-
543
-	db.Set("/webapp", "1")
544
-	db.Set("/db", "2")
545
-
546
-	db.Set("/db/webapp", "1")
547
-
548
-	if c := db.Refs("1"); c != 2 {
549
-		t.Fatalf("Expected 2 reference for webapp, got %d", c)
550
-	}
551
-	if c := db.Refs("2"); c != 1 {
552
-		t.Fatalf("Expected 1 reference for db, got %d", c)
553
-	}
554
-
555
-	if rp := db.RefPaths("2"); len(rp) != 1 {
556
-		t.Fatalf("Expected 1 reference path for db, got %d", len(rp))
557
-	}
558
-
559
-	count, err := db.Purge("2")
560
-	if err != nil {
561
-		t.Fatal(err)
562
-	}
563
-
564
-	if count != 2 {
565
-		t.Fatalf("Expected 2 rows to be removed, got %d", count)
566
-	}
567
-
568
-	if c := db.Refs("2"); c != 0 {
569
-		t.Fatalf("Expected 0 reference for db, got %d", c)
570
-	}
571
-	if c := db.Refs("1"); c != 1 {
572
-		t.Fatalf("Expected 1 reference for webapp, got %d", c)
573
-	}
574
-}
575
-
576
-func TestRename(t *testing.T) {
577
-	// TODO Windows: Port this test
578
-	if runtime.GOOS == "windows" {
579
-		t.Skip("Needs porting to Windows")
580
-	}
581
-	db, dbpath := newTestDb(t)
582
-	defer destroyTestDb(dbpath)
583
-
584
-	db.Set("/webapp", "1")
585
-
586
-	if db.Refs("1") != 1 {
587
-		t.Fatal("Expect reference count to be 1")
588
-	}
589
-
590
-	db.Set("/db", "2")
591
-	db.Set("/webapp/db", "2")
592
-
593
-	if db.Get("/webapp/db") == nil {
594
-		t.Fatal("Cannot find entity at path /webapp/db")
595
-	}
596
-
597
-	if err := db.Rename("/webapp/db", "/webapp/newdb"); err != nil {
598
-		t.Fatal(err)
599
-	}
600
-	if db.Get("/webapp/db") != nil {
601
-		t.Fatal("Entity should not exist at /webapp/db")
602
-	}
603
-	if db.Get("/webapp/newdb") == nil {
604
-		t.Fatal("Cannot find entity at path /webapp/newdb")
605
-	}
606
-
607
-}
608
-
609
-func TestCreateMultipleNames(t *testing.T) {
610
-	// TODO Windows: Port this test
611
-	if runtime.GOOS == "windows" {
612
-		t.Skip("Needs porting to Windows")
613
-	}
614
-
615
-	db, dbpath := newTestDb(t)
616
-	defer destroyTestDb(dbpath)
617
-
618
-	db.Set("/db", "1")
619
-	if _, err := db.Set("/myapp", "1"); err != nil {
620
-		t.Fatal(err)
621
-	}
622
-
623
-	db.Walk("/", func(p string, e *Entity) error {
624
-		t.Logf("%s\n", p)
625
-		return nil
626
-	}, -1)
627
-}
628
-
629
-func TestRefPaths(t *testing.T) {
630
-	db, dbpath := newTestDb(t)
631
-	defer destroyTestDb(dbpath)
632
-
633
-	db.Set("/webapp", "1")
634
-
635
-	db.Set("/db", "2")
636
-	db.Set("/webapp/db", "2")
637
-
638
-	refs := db.RefPaths("2")
639
-	if len(refs) != 2 {
640
-		t.Fatalf("Expected reference count to be 2, got %d", len(refs))
641
-	}
642
-}
643
-
644
-func TestExistsTrue(t *testing.T) {
645
-	db, dbpath := newTestDb(t)
646
-	defer destroyTestDb(dbpath)
647
-
648
-	db.Set("/testing", "1")
649
-
650
-	if !db.Exists("/testing") {
651
-		t.Fatalf("/tesing should exist")
652
-	}
653
-}
654
-
655
-func TestExistsFalse(t *testing.T) {
656
-	// TODO Windows: Port this test
657
-	if runtime.GOOS == "windows" {
658
-		t.Skip("Needs porting to Windows")
659
-	}
660
-	db, dbpath := newTestDb(t)
661
-	defer destroyTestDb(dbpath)
662
-
663
-	db.Set("/toerhe", "1")
664
-
665
-	if db.Exists("/testing") {
666
-		t.Fatalf("/tesing should not exist")
667
-	}
668
-
669
-}
670
-
671
-func TestGetNameWithTrailingSlash(t *testing.T) {
672
-	db, dbpath := newTestDb(t)
673
-	defer destroyTestDb(dbpath)
674
-
675
-	db.Set("/todo", "1")
676
-
677
-	e := db.Get("/todo/")
678
-	if e == nil {
679
-		t.Fatalf("Entity should not be nil")
680
-	}
681
-}
682
-
683
-func TestConcurrentWrites(t *testing.T) {
684
-	// TODO Windows: Port this test
685
-	if runtime.GOOS == "windows" {
686
-		t.Skip("Needs porting to Windows")
687
-	}
688
-	db, dbpath := newTestDb(t)
689
-	defer destroyTestDb(dbpath)
690
-
691
-	errs := make(chan error, 2)
692
-
693
-	save := func(name string, id string) {
694
-		if _, err := db.Set(fmt.Sprintf("/%s", name), id); err != nil {
695
-			errs <- err
696
-		}
697
-		errs <- nil
698
-	}
699
-	purge := func(id string) {
700
-		if _, err := db.Purge(id); err != nil {
701
-			errs <- err
702
-		}
703
-		errs <- nil
704
-	}
705
-
706
-	save("/1", "1")
707
-
708
-	go purge("1")
709
-	go save("/2", "2")
710
-
711
-	any := false
712
-	for i := 0; i < 2; i++ {
713
-		if err := <-errs; err != nil {
714
-			any = true
715
-			t.Log(err)
716
-		}
717
-	}
718
-	if any {
719
-		t.Fail()
720
-	}
721
-}
722 1
deleted file mode 100644
... ...
@@ -1,27 +0,0 @@
1
-package graphdb
2
-
3
-import "sort"
4
-
5
-type pathSorter struct {
6
-	paths []string
7
-	by    func(i, j string) bool
8
-}
9
-
10
-func sortByDepth(paths []string) {
11
-	s := &pathSorter{paths, func(i, j string) bool {
12
-		return PathDepth(i) > PathDepth(j)
13
-	}}
14
-	sort.Sort(s)
15
-}
16
-
17
-func (s *pathSorter) Len() int {
18
-	return len(s.paths)
19
-}
20
-
21
-func (s *pathSorter) Swap(i, j int) {
22
-	s.paths[i], s.paths[j] = s.paths[j], s.paths[i]
23
-}
24
-
25
-func (s *pathSorter) Less(i, j int) bool {
26
-	return s.by(s.paths[i], s.paths[j])
27
-}
28 1
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+package graphdb
1
+
2
+import "sort"
3
+
4
+type pathSorter struct {
5
+	paths []string
6
+	by    func(i, j string) bool
7
+}
8
+
9
+func sortByDepth(paths []string) {
10
+	s := &pathSorter{paths, func(i, j string) bool {
11
+		return PathDepth(i) > PathDepth(j)
12
+	}}
13
+	sort.Sort(s)
14
+}
15
+
16
+func (s *pathSorter) Len() int {
17
+	return len(s.paths)
18
+}
19
+
20
+func (s *pathSorter) Swap(i, j int) {
21
+	s.paths[i], s.paths[j] = s.paths[j], s.paths[i]
22
+}
23
+
24
+func (s *pathSorter) Less(i, j int) bool {
25
+	return s.by(s.paths[i], s.paths[j])
26
+}
0 27
new file mode 100644
... ...
@@ -0,0 +1,29 @@
0
+package graphdb
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+func TestSort(t *testing.T) {
7
+	paths := []string{
8
+		"/",
9
+		"/myreallylongname",
10
+		"/app/db",
11
+	}
12
+
13
+	sortByDepth(paths)
14
+
15
+	if len(paths) != 3 {
16
+		t.Fatalf("Expected 3 parts got %d", len(paths))
17
+	}
18
+
19
+	if paths[0] != "/app/db" {
20
+		t.Fatalf("Expected /app/db got %s", paths[0])
21
+	}
22
+	if paths[1] != "/myreallylongname" {
23
+		t.Fatalf("Expected /myreallylongname got %s", paths[1])
24
+	}
25
+	if paths[2] != "/" {
26
+		t.Fatalf("Expected / got %s", paths[2])
27
+	}
28
+}
0 29
deleted file mode 100644
... ...
@@ -1,29 +0,0 @@
1
-package graphdb
2
-
3
-import (
4
-	"testing"
5
-)
6
-
7
-func TestSort(t *testing.T) {
8
-	paths := []string{
9
-		"/",
10
-		"/myreallylongname",
11
-		"/app/db",
12
-	}
13
-
14
-	sortByDepth(paths)
15
-
16
-	if len(paths) != 3 {
17
-		t.Fatalf("Expected 3 parts got %d", len(paths))
18
-	}
19
-
20
-	if paths[0] != "/app/db" {
21
-		t.Fatalf("Expected /app/db got %s", paths[0])
22
-	}
23
-	if paths[1] != "/myreallylongname" {
24
-		t.Fatalf("Expected /myreallylongname got %s", paths[1])
25
-	}
26
-	if paths[2] != "/" {
27
-		t.Fatalf("Expected / got %s", paths[2])
28
-	}
29
-}
30 1
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+// +build !cgo !linux
1
+
2
+package graphdb
0 3
deleted file mode 100644
... ...
@@ -1,32 +0,0 @@
1
-package graphdb
2
-
3
-import (
4
-	"path"
5
-	"strings"
6
-)
7
-
8
-// Split p on /
9
-func split(p string) []string {
10
-	return strings.Split(p, "/")
11
-}
12
-
13
-// PathDepth returns the depth or number of / in a given path
14
-func PathDepth(p string) int {
15
-	parts := split(p)
16
-	if len(parts) == 2 && parts[1] == "" {
17
-		return 1
18
-	}
19
-	return len(parts)
20
-}
21
-
22
-func splitPath(p string) (parent, name string) {
23
-	if p[0] != '/' {
24
-		p = "/" + p
25
-	}
26
-	parent, name = path.Split(p)
27
-	l := len(parent)
28
-	if parent[l-1] == '/' {
29
-		parent = parent[:l-1]
30
-	}
31
-	return
32
-}
33 1
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+package graphdb
1
+
2
+import (
3
+	"path"
4
+	"strings"
5
+)
6
+
7
+// Split p on /
8
+func split(p string) []string {
9
+	return strings.Split(p, "/")
10
+}
11
+
12
+// PathDepth returns the depth or number of / in a given path
13
+func PathDepth(p string) int {
14
+	parts := split(p)
15
+	if len(parts) == 2 && parts[1] == "" {
16
+		return 1
17
+	}
18
+	return len(parts)
19
+}
20
+
21
+func splitPath(p string) (parent, name string) {
22
+	if p[0] != '/' {
23
+		p = "/" + p
24
+	}
25
+	parent, name = path.Split(p)
26
+	l := len(parent)
27
+	if parent[l-1] == '/' {
28
+		parent = parent[:l-1]
29
+	}
30
+	return
31
+}
... ...
@@ -15,68 +15,91 @@ var (
15 15
 		"amazing",
16 16
 		"angry",
17 17
 		"awesome",
18
-		"backstabbing",
19
-		"berserk",
20
-		"big",
18
+		"blissful",
21 19
 		"boring",
20
+		"brave",
22 21
 		"clever",
23 22
 		"cocky",
24 23
 		"compassionate",
24
+		"competent",
25 25
 		"condescending",
26
+		"confident",
26 27
 		"cranky",
27
-		"desperate",
28
+		"dazzling",
28 29
 		"determined",
29 30
 		"distracted",
30 31
 		"dreamy",
31
-		"drunk",
32 32
 		"eager",
33 33
 		"ecstatic",
34 34
 		"elastic",
35 35
 		"elated",
36 36
 		"elegant",
37
-		"evil",
37
+		"eloquent",
38
+		"epic",
38 39
 		"fervent",
40
+		"festive",
41
+		"flamboyant",
39 42
 		"focused",
40
-		"furious",
41
-		"gigantic",
42
-		"gloomy",
43
+		"friendly",
44
+		"frosty",
45
+		"gallant",
46
+		"gifted",
43 47
 		"goofy",
44
-		"grave",
48
+		"gracious",
45 49
 		"happy",
46
-		"high",
50
+		"hardcore",
51
+		"heuristic",
47 52
 		"hopeful",
48 53
 		"hungry",
49 54
 		"infallible",
55
+		"inspiring",
50 56
 		"jolly",
51 57
 		"jovial",
58
+		"keen",
52 59
 		"kickass",
53
-		"lonely",
60
+		"kind",
61
+		"laughing",
54 62
 		"loving",
55
-		"mad",
63
+		"lucid",
64
+		"mystifying",
56 65
 		"modest",
66
+		"musing",
57 67
 		"naughty",
58
-		"nauseous",
68
+		"nervous",
69
+		"nifty",
59 70
 		"nostalgic",
71
+		"objective",
72
+		"optimistic",
60 73
 		"peaceful",
61 74
 		"pedantic",
62 75
 		"pensive",
63
-		"prickly",
76
+		"practical",
77
+		"priceless",
78
+		"quirky",
79
+		"quizzical",
80
+		"relaxed",
64 81
 		"reverent",
65 82
 		"romantic",
66 83
 		"sad",
67 84
 		"serene",
68 85
 		"sharp",
69
-		"sick",
70 86
 		"silly",
71 87
 		"sleepy",
72
-		"small",
73 88
 		"stoic",
74 89
 		"stupefied",
75 90
 		"suspicious",
76 91
 		"tender",
77 92
 		"thirsty",
78
-		"tiny",
79 93
 		"trusting",
94
+		"unruffled",
95
+		"upbeat",
96
+		"vibrant",
97
+		"vigilant",
98
+		"wizardly",
99
+		"wonderful",
100
+		"xenodochial",
101
+		"youthful",
102
+		"zealous",
80 103
 		"zen",
81 104
 	}
82 105
 
... ...
@@ -83,15 +83,21 @@ func DumpStacks(dir string) (string, error) {
83 83
 		bufferLen *= 2
84 84
 	}
85 85
 	buf = buf[:stackSize]
86
-	path := filepath.Join(dir, fmt.Sprintf(stacksLogNameTemplate, strings.Replace(time.Now().Format(time.RFC3339), ":", "", -1)))
87
-	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666)
88
-	if err != nil {
89
-		return "", errors.Wrap(err, "failed to open file to write the goroutine stacks")
86
+	var f *os.File
87
+	if dir != "" {
88
+		path := filepath.Join(dir, fmt.Sprintf(stacksLogNameTemplate, strings.Replace(time.Now().Format(time.RFC3339), ":", "", -1)))
89
+		var err error
90
+		f, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0666)
91
+		if err != nil {
92
+			return "", errors.Wrap(err, "failed to open file to write the goroutine stacks")
93
+		}
94
+		defer f.Close()
95
+		defer f.Sync()
96
+	} else {
97
+		f = os.Stderr
90 98
 	}
91
-	defer f.Close()
92 99
 	if _, err := f.Write(buf); err != nil {
93 100
 		return "", errors.Wrap(err, "failed to write goroutine stacks")
94 101
 	}
95
-	f.Sync()
96
-	return path, nil
102
+	return f.Name(), nil
97 103
 }
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	"net/http"
12 12
 	"os"
13 13
 	"path/filepath"
14
+	"regexp"
14 15
 
15 16
 	"github.com/Sirupsen/logrus"
16 17
 	"github.com/docker/docker/api/types"
... ...
@@ -23,6 +24,11 @@ import (
23 23
 	"golang.org/x/net/context"
24 24
 )
25 25
 
26
+var (
27
+	validFullID    = regexp.MustCompile(`^([a-f0-9]{64})$`)
28
+	validPartialID = regexp.MustCompile(`^([a-f0-9]{1,64})$`)
29
+)
30
+
26 31
 // Disable deactivates a plugin, which implies that they cannot be used by containers.
27 32
 func (pm *Manager) Disable(name string) error {
28 33
 	p, err := pm.pluginStore.GetByName(name)
... ...
@@ -53,12 +59,32 @@ func (pm *Manager) Enable(name string, config *types.PluginEnableConfig) error {
53 53
 }
54 54
 
55 55
 // Inspect examines a plugin config
56
-func (pm *Manager) Inspect(name string) (tp types.Plugin, err error) {
57
-	p, err := pm.pluginStore.GetByName(name)
58
-	if err != nil {
56
+func (pm *Manager) Inspect(refOrID string) (tp types.Plugin, err error) {
57
+	// Match on full ID
58
+	if validFullID.MatchString(refOrID) {
59
+		p, err := pm.pluginStore.GetByID(refOrID)
60
+		if err == nil {
61
+			return p.PluginObj, nil
62
+		}
63
+	}
64
+
65
+	// Match on full name
66
+	if pluginName, err := getPluginName(refOrID); err == nil {
67
+		if p, err := pm.pluginStore.GetByName(pluginName); err == nil {
68
+			return p.PluginObj, nil
69
+		}
70
+	}
71
+
72
+	// Match on partial ID
73
+	if validPartialID.MatchString(refOrID) {
74
+		p, err := pm.pluginStore.Search(refOrID)
75
+		if err == nil {
76
+			return p.PluginObj, nil
77
+		}
59 78
 		return tp, err
60 79
 	}
61
-	return p.PluginObj, nil
80
+
81
+	return tp, fmt.Errorf("no plugin name or ID associated with %q", refOrID)
62 82
 }
63 83
 
64 84
 func (pm *Manager) pull(ref reference.Named, metaHeader http.Header, authConfig *types.AuthConfig, pluginID string) (types.PluginPrivileges, error) {
... ...
@@ -244,3 +270,18 @@ func (pm *Manager) createFromContext(ctx context.Context, pluginID, pluginDir st
244 244
 
245 245
 	return nil
246 246
 }
247
+
248
+func getPluginName(name string) (string, error) {
249
+	named, err := reference.ParseNamed(name) // FIXME: validate
250
+	if err != nil {
251
+		return "", err
252
+	}
253
+	if reference.IsNameOnly(named) {
254
+		named = reference.WithDefaultTag(named)
255
+	}
256
+	ref, ok := named.(reference.NamedTagged)
257
+	if !ok {
258
+		return "", fmt.Errorf("invalid name: %s", named.String())
259
+	}
260
+	return ref.String(), nil
261
+}
... ...
@@ -30,6 +30,13 @@ type ErrNotFound string
30 30
 
31 31
 func (name ErrNotFound) Error() string { return fmt.Sprintf("plugin %q not found", string(name)) }
32 32
 
33
+// ErrAmbiguous indicates that a plugin was not found locally.
34
+type ErrAmbiguous string
35
+
36
+func (name ErrAmbiguous) Error() string {
37
+	return fmt.Sprintf("multiple plugins found for %q", string(name))
38
+}
39
+
33 40
 // GetByName retreives a plugin by name.
34 41
 func (ps *Store) GetByName(name string) (*v2.Plugin, error) {
35 42
 	ps.RLock()
... ...
@@ -253,3 +260,25 @@ func (ps *Store) CallHandler(p *v2.Plugin) {
253 253
 		}
254 254
 	}
255 255
 }
256
+
257
+// Search retreives a plugin by ID Prefix
258
+// If no plugin is found, then ErrNotFound is returned
259
+// If multiple plugins are found, then ErrAmbiguous is returned
260
+func (ps *Store) Search(partialID string) (*v2.Plugin, error) {
261
+	ps.RLock()
262
+	defer ps.RUnlock()
263
+
264
+	var found *v2.Plugin
265
+	for id, p := range ps.plugins {
266
+		if strings.HasPrefix(id, partialID) {
267
+			if found != nil {
268
+				return nil, ErrAmbiguous(partialID)
269
+			}
270
+			found = p
271
+		}
272
+	}
273
+	if found == nil {
274
+		return nil, ErrNotFound(partialID)
275
+	}
276
+	return found, nil
277
+}
... ...
@@ -25,7 +25,6 @@ type ContainerOptions struct {
25 25
 	attach             opts.ListOpts
26 26
 	volumes            opts.ListOpts
27 27
 	tmpfs              opts.ListOpts
28
-	mounts             opts.MountOpt
29 28
 	blkioWeightDevice  WeightdeviceOpt
30 29
 	deviceReadBps      ThrottledeviceOpt
31 30
 	deviceWriteBps     ThrottledeviceOpt
... ...
@@ -217,7 +216,6 @@ func AddFlags(flags *pflag.FlagSet) *ContainerOptions {
217 217
 	flags.Var(&copts.tmpfs, "tmpfs", "Mount a tmpfs directory")
218 218
 	flags.Var(&copts.volumesFrom, "volumes-from", "Mount volumes from the specified container(s)")
219 219
 	flags.VarP(&copts.volumes, "volume", "v", "Bind mount a volume")
220
-	flags.Var(&copts.mounts, "mount", "Attach a filesystem mount to the container")
221 220
 
222 221
 	// Health-checking
223 222
 	flags.StringVar(&copts.healthCmd, "health-cmd", "", "Command to run to check health")
... ...
@@ -357,8 +355,6 @@ func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *c
357 357
 		}
358 358
 	}
359 359
 
360
-	mounts := copts.mounts.Value()
361
-
362 360
 	var binds []string
363 361
 	volumes := copts.volumes.GetMap()
364 362
 	// add any bind targets to the list of container volumes
... ...
@@ -623,7 +619,6 @@ func Parse(flags *pflag.FlagSet, copts *ContainerOptions) (*container.Config, *c
623 623
 		Tmpfs:          tmpfs,
624 624
 		Sysctls:        copts.sysctls.GetAll(),
625 625
 		Runtime:        copts.runtime,
626
-		Mounts:         mounts,
627 626
 	}
628 627
 
629 628
 	// only set this value if the user provided the flag, else it should default to nil