Compare commits
558 commits
master
...
1.10.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ecff6f1a9a | ||
|
|
f90618fc43 | ||
|
|
a45bb184f1 | ||
|
|
ae26fdf916 | ||
|
|
25853874c4 | ||
|
|
46f034705e | ||
|
|
4d85caf143 | ||
|
|
efda1efffe | ||
|
|
7e40754ffc | ||
|
|
70e90a07d3 | ||
|
|
b426868eda | ||
|
|
181a4e990e | ||
|
|
6d02f3fb23 | ||
|
|
f4c037d223 | ||
|
|
39d91cae95 | ||
|
|
f4125b3444 | ||
|
|
fcd38d3c4b | ||
|
|
f9dfb006b5 | ||
|
|
e6985de971 | ||
|
|
e002171ab1 | ||
|
|
f89ca3e147 | ||
|
|
24e9f66d79 | ||
|
|
a2da43b997 | ||
|
|
252d15a4a9 | ||
|
|
77b5ac4e54 | ||
|
|
a406378a1f | ||
|
|
29f9594ab9 | ||
|
|
ca529d36f8 | ||
|
|
455fde15c6 | ||
|
|
7d03c1fe29 | ||
|
|
0ca4d9c274 | ||
|
|
28788bd9b5 | ||
|
|
c36bb3f8d2 | ||
|
|
54103dc954 | ||
|
|
28546d1f81 | ||
|
|
08f1ea7a93 | ||
|
|
b05c6c6fe9 | ||
|
|
de90765531 | ||
|
|
e216a31f1e | ||
|
|
e8903da96c | ||
|
|
613e060f0d | ||
|
|
070f8b3992 | ||
|
|
016197c16e | ||
|
|
6abdd9cc32 | ||
|
|
fd7c16f1a4 | ||
|
|
20a511e961 | ||
|
|
9e18929d60 | ||
|
|
4b17aa1b9e | ||
|
|
bd7db570bd | ||
|
|
1c07d6453f | ||
|
|
093b040b8e | ||
|
|
b801f275d7 | ||
|
|
94f7016fb7 | ||
|
|
f3628c7a5e | ||
|
|
e115eaf6fc | ||
|
|
1bf0cd07de | ||
|
|
cefa239c2e | ||
|
|
6f3e4bbc6c | ||
|
|
87b6b3c139 | ||
|
|
1fb5c4b15a | ||
|
|
35ed189981 | ||
|
|
c392acc56b | ||
|
|
cd267d5121 | ||
|
|
cb076a57b9 | ||
|
|
6246a2592e | ||
|
|
606358cfb7 | ||
|
|
0488dd3709 | ||
|
|
b72f911ccf | ||
|
|
f9c5816ab8 | ||
|
|
2ecbf25445 | ||
|
|
8f842d55d7 | ||
|
|
ad19ff6c67 | ||
|
|
4fb7033d9c | ||
|
|
e1b7510e4a | ||
|
|
f7853a30bd | ||
|
|
3b4a53c959 | ||
|
|
7f54850b4a | ||
|
|
c72c966abc | ||
|
|
801167d271 | ||
|
|
408e4719e1 | ||
|
|
dbf40d8244 | ||
|
|
6021237a69 | ||
|
|
e4159cfd42 | ||
|
|
d8ec9c1572 | ||
|
|
244b303625 | ||
|
|
217f762a60 | ||
|
|
70da16103a | ||
|
|
6b71645ed7 | ||
|
|
05bf9a054a | ||
|
|
6e3d82eea6 | ||
|
|
21d114b879 | ||
|
|
72849d99c0 | ||
|
|
85e3ad2655 | ||
|
|
a3e30c3eed | ||
|
|
6fd77fa698 | ||
|
|
a7fc3e2220 | ||
|
|
db02c9f537 | ||
|
|
33cc601176 | ||
|
|
44e82edc5f | ||
|
|
53341b82f9 | ||
|
|
0de9a1b388 | ||
|
|
9bf6bc6dbd | ||
|
|
80afbd3961 | ||
|
|
de374d845e | ||
|
|
6c29830127 | ||
|
|
0a9ab358bf | ||
|
|
3c424b709e | ||
|
|
47a40d42c7 | ||
|
|
f316b448c2 | ||
|
|
6bfdde6855 | ||
|
|
2a08d4731e | ||
|
|
11d8093fc8 | ||
|
|
d0b46ca9b2 | ||
|
|
b7f9fc4b28 | ||
|
|
70a605acac | ||
|
|
85b85bc675 | ||
|
|
b334b6f059 | ||
|
|
0c1c338a02 | ||
|
|
f655a8af95 | ||
|
|
f7cd94d4a9 | ||
|
|
e4d2d7ed8a | ||
|
|
2a8c2c8ad6 | ||
|
|
5852db4d72 | ||
|
|
250a7a530b | ||
|
|
4e8b017283 | ||
|
|
a86a195c50 | ||
|
|
3368887a29 | ||
|
|
e5f1429ce1 | ||
|
|
65b0e5973b | ||
|
|
9cf483e224 | ||
|
|
1e164ca802 | ||
|
|
a2ded237e4 | ||
|
|
8a9ab69a1c | ||
|
|
9cfbfd55c4 | ||
|
|
d41e6e00fa | ||
|
|
e8da6cb631 | ||
|
|
0d7bf73446 | ||
|
|
ea2d526246 | ||
|
|
2160c787e3 | ||
|
|
ebae76bee8 | ||
|
|
720dc893e2 | ||
|
|
d03f4e4b32 | ||
|
|
275b54641a | ||
|
|
68b4ef6cf2 | ||
|
|
de6496c6c9 | ||
|
|
79edda6804 | ||
|
|
a81b9dc6a0 | ||
|
|
868133e881 | ||
|
|
d3899418b7 | ||
|
|
09e359fc8d | ||
|
|
cdef2b5e3b | ||
|
|
b865f35f17 | ||
|
|
73d57a1acb | ||
|
|
1dea8abe69 | ||
|
|
7dd29e8239 | ||
|
|
98d7a1e9dd | ||
|
|
a2adf31caa | ||
|
|
44715f18bd | ||
|
|
bd0f6d8d7b | ||
|
|
d434098b94 | ||
|
|
e863894e2d | ||
|
|
94afcfaf9d | ||
|
|
c6c1afd568 | ||
|
|
1ad88662c0 | ||
|
|
827573a594 | ||
|
|
9eb1204958 | ||
|
|
4d720279a0 | ||
|
|
796dc91eb1 | ||
|
|
a7da06d920 | ||
|
|
c1b7d6c6ad | ||
|
|
a75c16cb1b | ||
|
|
e08409f18d | ||
|
|
9d7dbe3857 | ||
|
|
bcd5286cd3 | ||
|
|
daebf74d6c | ||
|
|
155d813606 | ||
|
|
73c2f8ee37 | ||
|
|
4aae2c3b7b | ||
|
|
b79ad5f966 | ||
|
|
f8e3c46fbb | ||
|
|
f7c923062d | ||
|
|
61906ac2ff | ||
|
|
ea8032c115 | ||
|
|
7d22809ef4 | ||
|
|
cfda9d844e | ||
|
|
1e29ad9fc7 | ||
|
|
fcf78fe3de | ||
|
|
8d7b1e9047 | ||
|
|
6d2aa80435 | ||
|
|
13ec3d0217 | ||
|
|
f59fef09a6 | ||
|
|
f0a8c65b05 | ||
|
|
674e541cf7 | ||
|
|
ed5fedf516 | ||
|
|
654b3710f7 | ||
|
|
f4a22b94ed | ||
|
|
e5689afe4c | ||
|
|
42cb719b52 | ||
|
|
6ac6860dda | ||
|
|
353da73eab | ||
|
|
c686be8fd3 | ||
|
|
2f7a77e954 | ||
|
|
d78ea85301 | ||
|
|
7c95c733a9 | ||
|
|
dd9a8d6eee | ||
|
|
4f7c950ca8 | ||
|
|
a1d6e3b9e3 | ||
|
|
3eac70a9d3 | ||
|
|
c7687592ff | ||
|
|
155efd28fa | ||
|
|
2ced83e3d9 | ||
|
|
cea7911f56 | ||
|
|
db12794b1c | ||
|
|
deeca57a0d | ||
|
|
edcbe2eb4d | ||
|
|
ad00f3dd21 | ||
|
|
c77a8cfe3b | ||
|
|
8548b75582 | ||
|
|
c1be49ad53 | ||
|
|
a716bdc482 | ||
|
|
abec6f5891 | ||
|
|
c195915263 | ||
|
|
34d8f9b55a | ||
|
|
f5533c1ed8 | ||
|
|
722e3a2fc7 | ||
|
|
9503aa2b5f | ||
|
|
d99cad60e7 | ||
|
|
8199c4a6e1 | ||
|
|
19ae76a442 | ||
|
|
a55210413c | ||
|
|
6caa188730 | ||
|
|
413a55aa71 | ||
|
|
64336615cf | ||
|
|
a59982eb11 | ||
|
|
46f33f12b0 | ||
|
|
dc718eae65 | ||
|
|
aa5ff05463 | ||
|
|
3b7471ae84 | ||
|
|
7f009aeeb9 | ||
|
|
44c7d080bd | ||
|
|
32bd760526 | ||
|
|
009dbbe971 | ||
|
|
6b59ba0c31 | ||
|
|
61d00ebee4 | ||
|
|
3b1276bd44 | ||
|
|
3e8a4a5dc3 | ||
|
|
0c87e0b18f | ||
|
|
8024f2f09e | ||
|
|
f081376067 | ||
|
|
ce0d469c18 | ||
|
|
b2ee08f439 | ||
|
|
e9ba06ed4b | ||
|
|
9e9b36460c | ||
|
|
25df0d8147 | ||
|
|
c98c617c30 | ||
|
|
d3cd9213c1 | ||
|
|
bbaae11a0f | ||
|
|
e925b8272b | ||
|
|
110401b6f0 | ||
|
|
8fb90bd732 | ||
|
|
24e71db345 | ||
|
|
fbe8484377 | ||
|
|
89cca7bcb2 | ||
|
|
be66779fe9 | ||
|
|
1772909fe2 | ||
|
|
86bdab64ab | ||
|
|
36a10f8dd5 | ||
|
|
9249ec62c2 | ||
|
|
4537ec70cc | ||
|
|
695c692be6 | ||
|
|
297d20f085 | ||
|
|
16ef3d0eb8 | ||
|
|
d765a3fb91 | ||
|
|
7f4a94514b | ||
|
|
c0fe545947 | ||
|
|
52e74ab7ad | ||
|
|
2b46685855 | ||
|
|
2b7306967b | ||
|
|
3f28472ebc | ||
|
|
3da25aa463 | ||
|
|
a66bf72199 | ||
|
|
883227c4d8 | ||
|
|
5545c55ecc | ||
|
|
227fa5c0de | ||
|
|
d399b7893f | ||
|
|
833e16117e | ||
|
|
bf068a8287 | ||
|
|
b1ebf5ce17 | ||
|
|
836ec70979 | ||
|
|
6ca410fd6b | ||
|
|
38a6d04852 | ||
|
|
0952c1bb51 | ||
|
|
18a1829db0 | ||
|
|
e40de088f3 | ||
|
|
6fe54f5c24 | ||
|
|
5cfd947f38 | ||
|
|
513a6b35cc | ||
|
|
cbec6f8834 | ||
|
|
76bc06b729 | ||
|
|
c9ef1fa32f | ||
|
|
c52eed66b7 | ||
|
|
3f65bdcf46 | ||
|
|
6e5c312768 | ||
|
|
33bb7c4e02 | ||
|
|
bb377d3fe6 | ||
|
|
7442b416e8 | ||
|
|
fbee4ce4b3 | ||
|
|
8398382b65 | ||
|
|
7dd5fd5763 | ||
|
|
d5f3826ec7 | ||
|
|
20c936a251 | ||
|
|
d2556a1347 | ||
|
|
237f134a00 | ||
|
|
d4720f85ef | ||
|
|
64fc2b85cb | ||
|
|
a22d248390 | ||
|
|
fffedfc87b | ||
|
|
34fd042dbf | ||
|
|
89e31f7a8d | ||
|
|
b446c09735 | ||
|
|
8f48fa4747 | ||
|
|
7240ff35ee | ||
|
|
aaf66e3485 | ||
|
|
96f4a42a35 | ||
|
|
e6fbca42a1 | ||
|
|
527bf3b023 | ||
|
|
ab36c9c6cd | ||
|
|
e67419065a | ||
|
|
69e956ce8b | ||
|
|
0dbd99bad2 | ||
|
|
fa975d7fbe | ||
|
|
81f0e72bd2 | ||
|
|
da27f8e7e2 | ||
|
|
8572d50903 | ||
|
|
5d39813e1b | ||
|
|
b19315b57e | ||
|
|
e549875e89 | ||
|
|
7e21b05f05 | ||
|
|
bea2072b95 | ||
|
|
3b6cc7a7bb | ||
|
|
a264470cc0 | ||
|
|
844e2c3d26 | ||
|
|
210a14cf28 | ||
|
|
9ce4024951 | ||
|
|
8fb6fb7b19 | ||
|
|
83760d0e9e | ||
|
|
be5b7b6f0e | ||
|
|
e5a02d3052 | ||
|
|
3a395892fc | ||
|
|
09f6a876cf | ||
|
|
0117148a36 | ||
|
|
8f70c8cdeb | ||
|
|
16a74f3797 | ||
|
|
c42918ec7c | ||
|
|
d28b2027b8 | ||
|
|
8d816fc2f3 | ||
|
|
f476436027 | ||
|
|
fae20305ec | ||
|
|
4628e93fb2 | ||
|
|
82086a4e92 | ||
|
|
96e9b47059 | ||
|
|
34166ef5a4 | ||
|
|
285e52cc7c | ||
|
|
d52c969f94 | ||
|
|
63c3e6f58c | ||
|
|
0ab76bb8bc | ||
|
|
7fc577c31d | ||
|
|
3a43110f06 | ||
|
|
87d79d4d99 | ||
|
|
83581c3a0f | ||
|
|
ba90f55075 | ||
|
|
3313dcb1ce | ||
|
|
92d56fab47 | ||
|
|
1208f92d9c | ||
|
|
8444551373 | ||
|
|
73ebd7e560 | ||
|
|
0a96f86f74 | ||
|
|
36176befb0 | ||
|
|
de08da278d | ||
|
|
4c2eb17ccd | ||
|
|
8fb44db92b | ||
|
|
4105c3017c | ||
|
|
7f2f4eef48 | ||
|
|
666c3cb1c7 | ||
|
|
886134c1f3 | ||
|
|
ba61a6c5fb | ||
|
|
3f14df374f | ||
|
|
e6755d1e7c | ||
|
|
c4f59e731d | ||
|
|
805ed344c0 | ||
|
|
a5959d9be2 | ||
|
|
0375dccf64 | ||
|
|
3c4bb5358e | ||
|
|
e317d2db9d | ||
|
|
3daecfa8e4 | ||
|
|
cf93362368 | ||
|
|
23d4eda2a5 | ||
|
|
718ae13ae1 | ||
|
|
cddbe9fbf1 | ||
|
|
9c8173dbfd | ||
|
|
77ff37a853 | ||
|
|
40341674bd | ||
|
|
62ebdce5a9 | ||
|
|
58de4e0c26 | ||
|
|
dbd6c62b70 | ||
|
|
887c6753f8 | ||
|
|
bd35896892 | ||
|
|
9286e62449 | ||
|
|
621d1a5167 | ||
|
|
83714fbac2 | ||
|
|
413921a287 | ||
|
|
7ee36829ac | ||
|
|
bfb46b37d3 | ||
|
|
09d2bdbb21 | ||
|
|
8733d09a9c | ||
|
|
e524cce222 | ||
|
|
be6b811c4e | ||
|
|
bdb9a280bc | ||
|
|
73ca4eb599 | ||
|
|
569ccbadec | ||
|
|
ed1b584c42 | ||
|
|
2f2e946907 | ||
|
|
d392f70cc6 | ||
|
|
db164cefd3 | ||
|
|
4d613d3ba7 | ||
|
|
1f26841e23 | ||
|
|
9370cb033c | ||
|
|
ab0ddb593f | ||
|
|
f67503d9fd | ||
|
|
ce729b0721 | ||
|
|
8156cdc56e | ||
|
|
8cc8e61474 | ||
|
|
29b0ffe5e9 | ||
|
|
a772a0d7d7 | ||
|
|
6f0096c87b | ||
|
|
da41ed22f9 | ||
|
|
d6fa8596d2 | ||
|
|
a9b4fe768d | ||
|
|
88e53e177d | ||
|
|
e168fd03ca | ||
|
|
95a23eb682 | ||
|
|
f5ad363143 | ||
|
|
f290faf4ba | ||
|
|
725088a18b | ||
|
|
bf672ec340 | ||
|
|
0e4f9c9a66 | ||
|
|
5fdb75b541 | ||
|
|
24d4a1045a | ||
|
|
514f0650b2 | ||
|
|
20d34c8b14 | ||
|
|
6f45eb7959 | ||
|
|
49b98fa111 | ||
|
|
6048630a11 | ||
|
|
46de4411a7 | ||
|
|
883f251e7d | ||
|
|
284cda087e | ||
|
|
b2f9c182f3 | ||
|
|
558098d322 | ||
|
|
6571e079b9 | ||
|
|
49ca23c034 | ||
|
|
709bd9c363 | ||
|
|
8cecf2e02d | ||
|
|
d59c759cdd | ||
|
|
7b5d5fcd58 | ||
|
|
d01f712376 | ||
|
|
ac75d35927 | ||
|
|
605d7f26e7 | ||
|
|
b24ca75914 | ||
|
|
2b75741e5a | ||
|
|
7ff8c2b224 | ||
|
|
db31adc208 | ||
|
|
805f6a7683 | ||
|
|
d92f323e6d | ||
|
|
cf2dbf55b8 | ||
|
|
8d4c724c2d | ||
|
|
9cb2770da4 | ||
|
|
6a23491fa9 | ||
|
|
294b9742be | ||
|
|
a9b1f15f92 | ||
|
|
aa7c7cdf93 | ||
|
|
28139ab90d | ||
|
|
d0792b49fa | ||
|
|
5548aa5c79 | ||
|
|
16440ff055 | ||
|
|
7850d6de45 | ||
|
|
74b4fb89bb | ||
|
|
22ccf35fa1 | ||
|
|
7ad1fe24bd | ||
|
|
450ba978c1 | ||
|
|
3d6946417d | ||
|
|
31cf63b374 | ||
|
|
5c853c4a2c | ||
|
|
ad922cd7a1 | ||
|
|
49bafdc4cd | ||
|
|
989b2491b9 | ||
|
|
ca2ce3a034 | ||
|
|
dfe9dccab8 | ||
|
|
d456c3909d | ||
|
|
29ceef6d93 | ||
|
|
8cff440800 | ||
|
|
e5f6ae767d | ||
|
|
cd44179305 | ||
|
|
c3c5b354b8 | ||
|
|
95cf195dbd | ||
|
|
a80afd67ab | ||
|
|
4bc4d273ac | ||
|
|
4911c77134 | ||
|
|
c1b9a76a54 | ||
|
|
c31e25af72 | ||
|
|
c8295d36cc | ||
|
|
b12c29479e | ||
|
|
cd47829f3d | ||
|
|
4d4ef4e0b3 | ||
|
|
882ef2ccd8 | ||
|
|
d6cd76c3c1 | ||
|
|
bd0be2cdc7 | ||
|
|
a8d7ebd987 | ||
|
|
00f61196a4 | ||
|
|
c21d6706b6 | ||
|
|
c3c5d91c47 | ||
|
|
7fa4cd1214 | ||
|
|
f353d9fbc0 | ||
|
|
09018855ce | ||
|
|
719954b02f | ||
|
|
67bc3fabe4 | ||
|
|
e724a346c7 | ||
|
|
87b4545b44 | ||
|
|
58a7844129 | ||
|
|
4353f7b9f9 | ||
|
|
8f8693e13e | ||
|
|
363a6563c7 | ||
|
|
59d6af73fa | ||
|
|
cd7f67018e | ||
|
|
b7e8770c4f | ||
|
|
ad4cc5d6df | ||
|
|
ca14ed68f7 | ||
|
|
71514cb380 | ||
|
|
8212f1bd45 | ||
|
|
dca3bbdea3 | ||
|
|
8ed7dfef6f | ||
|
|
631f5be02f | ||
|
|
4f4ea2a402 | ||
|
|
5a5bffebd1 | ||
|
|
8749bc0844 | ||
|
|
f3d0c63db2 | ||
|
|
93a846db31 | ||
|
|
686c25d50f | ||
|
|
ef6555f084 | ||
|
|
3c6652c101 | ||
|
|
43af1684c1 | ||
|
|
ed549155b3 | ||
|
|
39ae91c81c | ||
|
|
b6acb3cd8c | ||
|
|
a467a8a094 | ||
|
|
78227c3c06 | ||
|
|
e4e802d1f8 | ||
|
|
b24a60ba9f | ||
|
|
461b600068 |
48 changed files with 187 additions and 1448 deletions
63
CHANGELOG.md
63
CHANGELOG.md
|
|
@ -1,53 +1,6 @@
|
||||||
Change log
|
Change log
|
||||||
==========
|
==========
|
||||||
|
|
||||||
1.11.0 (2017-02-08)
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
### New Features
|
|
||||||
|
|
||||||
#### Compose file version 3.1
|
|
||||||
|
|
||||||
- Introduced version 3.1 of the `docker-compose.yml` specification. This
|
|
||||||
version requires Docker Engine 1.13.0 or above. It introduces support
|
|
||||||
for secrets. See the documentation for more information
|
|
||||||
|
|
||||||
#### Compose file version 2.0 and up
|
|
||||||
|
|
||||||
- Introduced the `docker-compose top` command that displays processes running
|
|
||||||
for the different services managed by Compose.
|
|
||||||
|
|
||||||
### Bugfixes
|
|
||||||
|
|
||||||
- Fixed a bug where extending a service defining a healthcheck dictionary
|
|
||||||
would cause `docker-compose` to error out.
|
|
||||||
|
|
||||||
- Fixed an issue where the `pid` entry in a service definition was being
|
|
||||||
ignored when using multiple Compose files.
|
|
||||||
|
|
||||||
1.10.1 (2017-02-01)
|
|
||||||
------------------
|
|
||||||
|
|
||||||
### Bugfixes
|
|
||||||
|
|
||||||
- Fixed an issue where presence of older versions of the docker-py
|
|
||||||
package would cause unexpected crashes while running Compose
|
|
||||||
|
|
||||||
- Fixed an issue where healthcheck dependencies would be lost when
|
|
||||||
using multiple compose files for a project
|
|
||||||
|
|
||||||
- Fixed a few issues that made the output of the `config` command
|
|
||||||
invalid
|
|
||||||
|
|
||||||
- Fixed an issue where adding volume labels to v3 Compose files would
|
|
||||||
result in an error
|
|
||||||
|
|
||||||
- Fixed an issue on Windows where build context paths containing unicode
|
|
||||||
characters were being improperly encoded
|
|
||||||
|
|
||||||
- Fixed a bug where Compose would occasionally crash while streaming logs
|
|
||||||
when containers would stop or restart
|
|
||||||
|
|
||||||
1.10.0 (2017-01-18)
|
1.10.0 (2017-01-18)
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
|
@ -59,6 +12,8 @@ Change log
|
||||||
version requires to be used with Docker Engine 1.13 or above and is
|
version requires to be used with Docker Engine 1.13 or above and is
|
||||||
specifically designed to work with the `docker stack` commands.
|
specifically designed to work with the `docker stack` commands.
|
||||||
|
|
||||||
|
- Added support for the `stop_grace_period` option in service definitions.
|
||||||
|
|
||||||
#### Compose file version 2.1 and up
|
#### Compose file version 2.1 and up
|
||||||
|
|
||||||
- Healthcheck configuration can now be done in the service definition using
|
- Healthcheck configuration can now be done in the service definition using
|
||||||
|
|
@ -75,10 +30,6 @@ Change log
|
||||||
|
|
||||||
- Compose now adds identifying labels to networks and volumes it creates
|
- Compose now adds identifying labels to networks and volumes it creates
|
||||||
|
|
||||||
#### Compose file version 2.0 and up
|
|
||||||
|
|
||||||
- Added support for the `stop_grace_period` option in service definitions.
|
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
|
||||||
- Colored output now works properly on Windows.
|
- Colored output now works properly on Windows.
|
||||||
|
|
@ -89,12 +40,6 @@ Change log
|
||||||
- Networks created by Compose are now always made attachable
|
- Networks created by Compose are now always made attachable
|
||||||
(Compose files v2.1 and up).
|
(Compose files v2.1 and up).
|
||||||
|
|
||||||
- Fixed a bug where falsy values of `COMPOSE_CONVERT_WINDOWS_PATHS`
|
|
||||||
(`0`, `false`, empty value) were being interpreted as true.
|
|
||||||
|
|
||||||
- Fixed a bug where forward slashes in some .dockerignore patterns weren't
|
|
||||||
being parsed correctly on Windows
|
|
||||||
|
|
||||||
|
|
||||||
1.9.0 (2016-11-16)
|
1.9.0 (2016-11-16)
|
||||||
-----------------
|
-----------------
|
||||||
|
|
@ -238,7 +183,7 @@ Bug Fixes
|
||||||
- Fixed a bug in Windows environment where volume mappings of the
|
- Fixed a bug in Windows environment where volume mappings of the
|
||||||
host's root directory would be parsed incorrectly.
|
host's root directory would be parsed incorrectly.
|
||||||
|
|
||||||
- Fixed a bug where `docker-compose config` would output an invalid
|
- Fixed a bug where `docker-compose config` would ouput an invalid
|
||||||
Compose file if external networks were specified.
|
Compose file if external networks were specified.
|
||||||
|
|
||||||
- Fixed an issue where unset buildargs would be assigned a string
|
- Fixed an issue where unset buildargs would be assigned a string
|
||||||
|
|
@ -620,7 +565,7 @@ Bug Fixes:
|
||||||
if at least one container is using the network.
|
if at least one container is using the network.
|
||||||
|
|
||||||
- When printings logs during `up` or `logs`, flush the output buffer after
|
- When printings logs during `up` or `logs`, flush the output buffer after
|
||||||
each line to prevent buffering issues from hiding logs.
|
each line to prevent buffering issues from hideing logs.
|
||||||
|
|
||||||
- Recreate a container if one of its dependencies is being created.
|
- Recreate a container if one of its dependencies is being created.
|
||||||
Previously a container was only recreated if it's dependencies already
|
Previously a container was only recreated if it's dependencies already
|
||||||
|
|
|
||||||
25
Dockerfile
25
Dockerfile
|
|
@ -13,7 +13,6 @@ RUN set -ex; \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
curl \
|
curl \
|
||||||
libsqlite3-dev \
|
libsqlite3-dev \
|
||||||
libbz2-dev \
|
|
||||||
; \
|
; \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
@ -21,32 +20,40 @@ RUN curl https://get.docker.com/builds/Linux/x86_64/docker-1.8.3 \
|
||||||
-o /usr/local/bin/docker && \
|
-o /usr/local/bin/docker && \
|
||||||
chmod +x /usr/local/bin/docker
|
chmod +x /usr/local/bin/docker
|
||||||
|
|
||||||
# Build Python 2.7.13 from source
|
# Build Python 2.7.9 from source
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
curl -L https://www.python.org/ftp/python/2.7.13/Python-2.7.13.tgz | tar -xz; \
|
curl -L https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tgz | tar -xz; \
|
||||||
cd Python-2.7.13; \
|
cd Python-2.7.9; \
|
||||||
./configure --enable-shared; \
|
./configure --enable-shared; \
|
||||||
make; \
|
make; \
|
||||||
make install; \
|
make install; \
|
||||||
cd ..; \
|
cd ..; \
|
||||||
rm -rf /Python-2.7.13
|
rm -rf /Python-2.7.9
|
||||||
|
|
||||||
# Build python 3.4 from source
|
# Build python 3.4 from source
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
curl -L https://www.python.org/ftp/python/3.4.6/Python-3.4.6.tgz | tar -xz; \
|
curl -L https://www.python.org/ftp/python/3.4.3/Python-3.4.3.tgz | tar -xz; \
|
||||||
cd Python-3.4.6; \
|
cd Python-3.4.3; \
|
||||||
./configure --enable-shared; \
|
./configure --enable-shared; \
|
||||||
make; \
|
make; \
|
||||||
make install; \
|
make install; \
|
||||||
cd ..; \
|
cd ..; \
|
||||||
rm -rf /Python-3.4.6
|
rm -rf /Python-3.4.3
|
||||||
|
|
||||||
# Make libpython findable
|
# Make libpython findable
|
||||||
ENV LD_LIBRARY_PATH /usr/local/lib
|
ENV LD_LIBRARY_PATH /usr/local/lib
|
||||||
|
|
||||||
|
# Install setuptools
|
||||||
|
RUN set -ex; \
|
||||||
|
curl -L https://bootstrap.pypa.io/ez_setup.py | python
|
||||||
|
|
||||||
# Install pip
|
# Install pip
|
||||||
RUN set -ex; \
|
RUN set -ex; \
|
||||||
curl -L https://bootstrap.pypa.io/get-pip.py | python
|
curl -L https://pypi.python.org/packages/source/p/pip/pip-8.1.1.tar.gz | tar -xz; \
|
||||||
|
cd pip-8.1.1; \
|
||||||
|
python setup.py install; \
|
||||||
|
cd ..; \
|
||||||
|
rm -rf pip-8.1.1
|
||||||
|
|
||||||
# Python3 requires a valid locale
|
# Python3 requires a valid locale
|
||||||
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
|
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
|
|
||||||
FROM alpine:3.4
|
FROM alpine:3.4
|
||||||
ARG version
|
|
||||||
RUN apk -U add \
|
RUN apk -U add \
|
||||||
python \
|
python \
|
||||||
py-pip
|
py-pip
|
||||||
|
|
@ -8,7 +7,7 @@ RUN apk -U add \
|
||||||
COPY requirements.txt /code/requirements.txt
|
COPY requirements.txt /code/requirements.txt
|
||||||
RUN pip install -r /code/requirements.txt
|
RUN pip install -r /code/requirements.txt
|
||||||
|
|
||||||
COPY dist/docker_compose-${version}-py2.py3-none-any.whl /code/
|
ADD dist/docker-compose-release.tar.gz /code/docker-compose
|
||||||
RUN pip install --no-deps /code/docker_compose-${version}-py2.py3-none-any.whl
|
RUN pip install --no-deps /code/docker-compose/docker-compose-*
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/bin/docker-compose"]
|
ENTRYPOINT ["/usr/bin/docker-compose"]
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ A `docker-compose.yml` looks like this:
|
||||||
image: redis
|
image: redis
|
||||||
|
|
||||||
For more information about the Compose file, see the
|
For more information about the Compose file, see the
|
||||||
[Compose file reference](https://github.com/docker/docker.github.io/blob/master/compose/compose-file/compose-versioning.md)
|
[Compose file reference](https://github.com/docker/docker.github.io/blob/master/compose/compose-file.md)
|
||||||
|
|
||||||
Compose has commands for managing the whole lifecycle of your application:
|
Compose has commands for managing the whole lifecycle of your application:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
__version__ = '1.12.0dev'
|
__version__ = '1.10.0-rc1'
|
||||||
|
|
|
||||||
|
|
@ -202,7 +202,7 @@ def convert_service_to_bundle(name, service_dict, image_digest):
|
||||||
return container_config
|
return container_config
|
||||||
|
|
||||||
|
|
||||||
# See https://github.com/docker/swarmkit/blob/agent/exec/container/container.go#L95
|
# See https://github.com/docker/swarmkit/blob//agent/exec/container/container.go#L95
|
||||||
def set_command_and_args(config, entrypoint, command):
|
def set_command_and_args(config, entrypoint, command):
|
||||||
if isinstance(entrypoint, six.string_types):
|
if isinstance(entrypoint, six.string_types):
|
||||||
entrypoint = split_command(entrypoint)
|
entrypoint = split_command(entrypoint)
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# Attempt to detect https://github.com/docker/compose/issues/4344
|
|
||||||
try:
|
|
||||||
# We don't try importing pip because it messes with package imports
|
|
||||||
# on some Linux distros (Ubuntu, Fedora)
|
|
||||||
# https://github.com/docker/compose/issues/4425
|
|
||||||
# https://github.com/docker/compose/issues/4481
|
|
||||||
# https://github.com/pypa/pip/blob/master/pip/_vendor/__init__.py
|
|
||||||
s_cmd = subprocess.Popen(
|
|
||||||
['pip', 'freeze'], stderr=subprocess.PIPE, stdout=subprocess.PIPE
|
|
||||||
)
|
|
||||||
packages = s_cmd.communicate()[0].splitlines()
|
|
||||||
dockerpy_installed = len(
|
|
||||||
list(filter(lambda p: p.startswith(b'docker-py=='), packages))
|
|
||||||
) > 0
|
|
||||||
if dockerpy_installed:
|
|
||||||
from .colors import red
|
|
||||||
print(
|
|
||||||
red('ERROR:'),
|
|
||||||
"Dependency conflict: an older version of the 'docker-py' package "
|
|
||||||
"is polluting the namespace. "
|
|
||||||
"Run the following command to remedy the issue:\n"
|
|
||||||
"pip uninstall docker docker-py; pip install docker",
|
|
||||||
file=sys.stderr
|
|
||||||
)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
except OSError:
|
|
||||||
# pip command is not available, which indicates it's probably the binary
|
|
||||||
# distribution of Compose which is not affected
|
|
||||||
pass
|
|
||||||
|
|
@ -33,7 +33,7 @@ def make_color_fn(code):
|
||||||
return lambda s: ansi_color(code, s)
|
return lambda s: ansi_color(code, s)
|
||||||
|
|
||||||
|
|
||||||
colorama.init(strip=False)
|
colorama.init()
|
||||||
for (name, code) in get_pairs():
|
for (name, code) in get_pairs():
|
||||||
globals()[name] = make_color_fn(code)
|
globals()[name] = make_color_fn(code)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,6 @@ class TopLevelCommand(object):
|
||||||
scale Set number of containers for a service
|
scale Set number of containers for a service
|
||||||
start Start services
|
start Start services
|
||||||
stop Stop services
|
stop Stop services
|
||||||
top Display the running processes
|
|
||||||
unpause Unpause services
|
unpause Unpause services
|
||||||
up Create and start containers
|
up Create and start containers
|
||||||
version Show the Docker-Compose version information
|
version Show the Docker-Compose version information
|
||||||
|
|
@ -777,33 +776,6 @@ class TopLevelCommand(object):
|
||||||
containers = self.project.restart(service_names=options['SERVICE'], timeout=timeout)
|
containers = self.project.restart(service_names=options['SERVICE'], timeout=timeout)
|
||||||
exit_if(not containers, 'No containers to restart', 1)
|
exit_if(not containers, 'No containers to restart', 1)
|
||||||
|
|
||||||
def top(self, options):
|
|
||||||
"""
|
|
||||||
Display the running processes
|
|
||||||
|
|
||||||
Usage: top [SERVICE...]
|
|
||||||
|
|
||||||
"""
|
|
||||||
containers = sorted(
|
|
||||||
self.project.containers(service_names=options['SERVICE'], stopped=False) +
|
|
||||||
self.project.containers(service_names=options['SERVICE'], one_off=OneOffFilter.only),
|
|
||||||
key=attrgetter('name')
|
|
||||||
)
|
|
||||||
|
|
||||||
for idx, container in enumerate(containers):
|
|
||||||
if idx > 0:
|
|
||||||
print()
|
|
||||||
|
|
||||||
top_data = self.project.client.top(container.name)
|
|
||||||
headers = top_data.get("Titles")
|
|
||||||
rows = []
|
|
||||||
|
|
||||||
for process in top_data.get("Processes", []):
|
|
||||||
rows.append(process)
|
|
||||||
|
|
||||||
print(container.name)
|
|
||||||
print(Formatter().table(headers, rows))
|
|
||||||
|
|
||||||
def unpause(self, options):
|
def unpause(self, options):
|
||||||
"""
|
"""
|
||||||
Unpause services.
|
Unpause services.
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,10 @@ import six
|
||||||
import yaml
|
import yaml
|
||||||
from cached_property import cached_property
|
from cached_property import cached_property
|
||||||
|
|
||||||
from . import types
|
|
||||||
from ..const import COMPOSEFILE_V1 as V1
|
from ..const import COMPOSEFILE_V1 as V1
|
||||||
from ..const import COMPOSEFILE_V2_0 as V2_0
|
from ..const import COMPOSEFILE_V2_0 as V2_0
|
||||||
from ..const import COMPOSEFILE_V2_1 as V2_1
|
from ..const import COMPOSEFILE_V2_1 as V2_1
|
||||||
from ..const import COMPOSEFILE_V3_0 as V3_0
|
from ..const import COMPOSEFILE_V3_0 as V3_0
|
||||||
from ..const import COMPOSEFILE_V3_1 as V3_1
|
|
||||||
from ..utils import build_string_dict
|
from ..utils import build_string_dict
|
||||||
from ..utils import parse_nanoseconds_int
|
from ..utils import parse_nanoseconds_int
|
||||||
from ..utils import splitdrive
|
from ..utils import splitdrive
|
||||||
|
|
@ -78,13 +76,12 @@ DOCKER_CONFIG_KEYS = [
|
||||||
'memswap_limit',
|
'memswap_limit',
|
||||||
'mem_swappiness',
|
'mem_swappiness',
|
||||||
'net',
|
'net',
|
||||||
'oom_score_adj',
|
'oom_score_adj'
|
||||||
'pid',
|
'pid',
|
||||||
'ports',
|
'ports',
|
||||||
'privileged',
|
'privileged',
|
||||||
'read_only',
|
'read_only',
|
||||||
'restart',
|
'restart',
|
||||||
'secrets',
|
|
||||||
'security_opt',
|
'security_opt',
|
||||||
'shm_size',
|
'shm_size',
|
||||||
'stdin_open',
|
'stdin_open',
|
||||||
|
|
@ -186,6 +183,11 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
|
||||||
if version == '3':
|
if version == '3':
|
||||||
version = V3_0
|
version = V3_0
|
||||||
|
|
||||||
|
if version not in (V2_0, V2_1, V3_0):
|
||||||
|
raise ConfigurationError(
|
||||||
|
'Version in "{}" is unsupported. {}'
|
||||||
|
.format(self.filename, VERSION_EXPLANATION))
|
||||||
|
|
||||||
return version
|
return version
|
||||||
|
|
||||||
def get_service(self, name):
|
def get_service(self, name):
|
||||||
|
|
@ -200,11 +202,8 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
|
||||||
def get_networks(self):
|
def get_networks(self):
|
||||||
return {} if self.version == V1 else self.config.get('networks', {})
|
return {} if self.version == V1 else self.config.get('networks', {})
|
||||||
|
|
||||||
def get_secrets(self):
|
|
||||||
return {} if self.version < V3_1 else self.config.get('secrets', {})
|
|
||||||
|
|
||||||
|
class Config(namedtuple('_Config', 'version services volumes networks')):
|
||||||
class Config(namedtuple('_Config', 'version services volumes networks secrets')):
|
|
||||||
"""
|
"""
|
||||||
:param version: configuration version
|
:param version: configuration version
|
||||||
:type version: int
|
:type version: int
|
||||||
|
|
@ -329,7 +328,6 @@ def load(config_details):
|
||||||
networks = load_mapping(
|
networks = load_mapping(
|
||||||
config_details.config_files, 'get_networks', 'Network'
|
config_details.config_files, 'get_networks', 'Network'
|
||||||
)
|
)
|
||||||
secrets = load_secrets(config_details.config_files, config_details.working_dir)
|
|
||||||
service_dicts = load_services(config_details, main_file)
|
service_dicts = load_services(config_details, main_file)
|
||||||
|
|
||||||
if main_file.version != V1:
|
if main_file.version != V1:
|
||||||
|
|
@ -344,7 +342,7 @@ def load(config_details):
|
||||||
"`docker stack deploy` to deploy to a swarm."
|
"`docker stack deploy` to deploy to a swarm."
|
||||||
.format(", ".join(sorted(s['name'] for s in services_using_deploy))))
|
.format(", ".join(sorted(s['name'] for s in services_using_deploy))))
|
||||||
|
|
||||||
return Config(main_file.version, service_dicts, volumes, networks, secrets)
|
return Config(main_file.version, service_dicts, volumes, networks)
|
||||||
|
|
||||||
|
|
||||||
def load_mapping(config_files, get_func, entity_type):
|
def load_mapping(config_files, get_func, entity_type):
|
||||||
|
|
@ -358,12 +356,22 @@ def load_mapping(config_files, get_func, entity_type):
|
||||||
|
|
||||||
external = config.get('external')
|
external = config.get('external')
|
||||||
if external:
|
if external:
|
||||||
validate_external(entity_type, name, config)
|
if len(config.keys()) > 1:
|
||||||
|
raise ConfigurationError(
|
||||||
|
'{} {} declared as external but specifies'
|
||||||
|
' additional attributes ({}). '.format(
|
||||||
|
entity_type,
|
||||||
|
name,
|
||||||
|
', '.join([k for k in config.keys() if k != 'external'])
|
||||||
|
)
|
||||||
|
)
|
||||||
if isinstance(external, dict):
|
if isinstance(external, dict):
|
||||||
config['external_name'] = external.get('name')
|
config['external_name'] = external.get('name')
|
||||||
else:
|
else:
|
||||||
config['external_name'] = name
|
config['external_name'] = name
|
||||||
|
|
||||||
|
mapping[name] = config
|
||||||
|
|
||||||
if 'driver_opts' in config:
|
if 'driver_opts' in config:
|
||||||
config['driver_opts'] = build_string_dict(
|
config['driver_opts'] = build_string_dict(
|
||||||
config['driver_opts']
|
config['driver_opts']
|
||||||
|
|
@ -375,39 +383,6 @@ def load_mapping(config_files, get_func, entity_type):
|
||||||
return mapping
|
return mapping
|
||||||
|
|
||||||
|
|
||||||
def validate_external(entity_type, name, config):
|
|
||||||
if len(config.keys()) <= 1:
|
|
||||||
return
|
|
||||||
|
|
||||||
raise ConfigurationError(
|
|
||||||
"{} {} declared as external but specifies additional attributes "
|
|
||||||
"({}).".format(
|
|
||||||
entity_type, name, ', '.join(k for k in config if k != 'external')))
|
|
||||||
|
|
||||||
|
|
||||||
def load_secrets(config_files, working_dir):
|
|
||||||
mapping = {}
|
|
||||||
|
|
||||||
for config_file in config_files:
|
|
||||||
for name, config in config_file.get_secrets().items():
|
|
||||||
mapping[name] = config or {}
|
|
||||||
if not config:
|
|
||||||
continue
|
|
||||||
|
|
||||||
external = config.get('external')
|
|
||||||
if external:
|
|
||||||
validate_external('Secret', name, config)
|
|
||||||
if isinstance(external, dict):
|
|
||||||
config['external_name'] = external.get('name')
|
|
||||||
else:
|
|
||||||
config['external_name'] = name
|
|
||||||
|
|
||||||
if 'file' in config:
|
|
||||||
config['file'] = expand_path(working_dir, config['file'])
|
|
||||||
|
|
||||||
return mapping
|
|
||||||
|
|
||||||
|
|
||||||
def load_services(config_details, config_file):
|
def load_services(config_details, config_file):
|
||||||
def build_service(service_name, service_dict, service_names):
|
def build_service(service_name, service_dict, service_names):
|
||||||
service_config = ServiceConfig.with_abs_paths(
|
service_config = ServiceConfig.with_abs_paths(
|
||||||
|
|
@ -474,7 +449,7 @@ def process_config_file(config_file, environment, service_name=None):
|
||||||
'service',
|
'service',
|
||||||
environment)
|
environment)
|
||||||
|
|
||||||
if config_file.version in (V2_0, V2_1, V3_0, V3_1):
|
if config_file.version in (V2_0, V2_1, V3_0):
|
||||||
processed_config = dict(config_file.config)
|
processed_config = dict(config_file.config)
|
||||||
processed_config['services'] = services
|
processed_config['services'] = services
|
||||||
processed_config['volumes'] = interpolate_config_section(
|
processed_config['volumes'] = interpolate_config_section(
|
||||||
|
|
@ -490,9 +465,7 @@ def process_config_file(config_file, environment, service_name=None):
|
||||||
elif config_file.version == V1:
|
elif config_file.version == V1:
|
||||||
processed_config = services
|
processed_config = services
|
||||||
else:
|
else:
|
||||||
raise ConfigurationError(
|
raise Exception("Unsupported version: {}".format(repr(config_file.version)))
|
||||||
'Version in "{}" is unsupported. {}'
|
|
||||||
.format(config_file.filename, VERSION_EXPLANATION))
|
|
||||||
|
|
||||||
config_file = config_file._replace(config=processed_config)
|
config_file = config_file._replace(config=processed_config)
|
||||||
validate_against_config_schema(config_file)
|
validate_against_config_schema(config_file)
|
||||||
|
|
@ -713,15 +686,9 @@ def process_healthcheck(service_dict, service_name):
|
||||||
hc['test'] = raw['test']
|
hc['test'] = raw['test']
|
||||||
|
|
||||||
if 'interval' in raw:
|
if 'interval' in raw:
|
||||||
if not isinstance(raw['interval'], six.integer_types):
|
|
||||||
hc['interval'] = parse_nanoseconds_int(raw['interval'])
|
hc['interval'] = parse_nanoseconds_int(raw['interval'])
|
||||||
else: # Conversion has been done previously
|
|
||||||
hc['interval'] = raw['interval']
|
|
||||||
if 'timeout' in raw:
|
if 'timeout' in raw:
|
||||||
if not isinstance(raw['timeout'], six.integer_types):
|
|
||||||
hc['timeout'] = parse_nanoseconds_int(raw['timeout'])
|
hc['timeout'] = parse_nanoseconds_int(raw['timeout'])
|
||||||
else: # Conversion has been done previously
|
|
||||||
hc['timeout'] = raw['timeout']
|
|
||||||
if 'retries' in raw:
|
if 'retries' in raw:
|
||||||
hc['retries'] = raw['retries']
|
hc['retries'] = raw['retries']
|
||||||
|
|
||||||
|
|
@ -745,7 +712,7 @@ def finalize_service(service_config, service_names, version, environment):
|
||||||
if 'volumes' in service_dict:
|
if 'volumes' in service_dict:
|
||||||
service_dict['volumes'] = [
|
service_dict['volumes'] = [
|
||||||
VolumeSpec.parse(
|
VolumeSpec.parse(
|
||||||
v, environment.get_boolean('COMPOSE_CONVERT_WINDOWS_PATHS')
|
v, environment.get('COMPOSE_CONVERT_WINDOWS_PATHS')
|
||||||
) for v in service_dict['volumes']
|
) for v in service_dict['volumes']
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -763,11 +730,6 @@ def finalize_service(service_config, service_names, version, environment):
|
||||||
if 'restart' in service_dict:
|
if 'restart' in service_dict:
|
||||||
service_dict['restart'] = parse_restart_spec(service_dict['restart'])
|
service_dict['restart'] = parse_restart_spec(service_dict['restart'])
|
||||||
|
|
||||||
if 'secrets' in service_dict:
|
|
||||||
service_dict['secrets'] = [
|
|
||||||
types.ServiceSecret.parse(s) for s in service_dict['secrets']
|
|
||||||
]
|
|
||||||
|
|
||||||
normalize_build(service_dict, service_config.working_dir, environment)
|
normalize_build(service_dict, service_config.working_dir, environment)
|
||||||
|
|
||||||
service_dict['name'] = service_config.name
|
service_dict['name'] = service_config.name
|
||||||
|
|
@ -856,16 +818,14 @@ def merge_service_dicts(base, override, version):
|
||||||
md.merge_mapping('ulimits', parse_ulimits)
|
md.merge_mapping('ulimits', parse_ulimits)
|
||||||
md.merge_mapping('networks', parse_networks)
|
md.merge_mapping('networks', parse_networks)
|
||||||
md.merge_mapping('sysctls', parse_sysctls)
|
md.merge_mapping('sysctls', parse_sysctls)
|
||||||
md.merge_mapping('depends_on', parse_depends_on)
|
|
||||||
md.merge_sequence('links', ServiceLink.parse)
|
md.merge_sequence('links', ServiceLink.parse)
|
||||||
md.merge_sequence('secrets', types.ServiceSecret.parse)
|
|
||||||
|
|
||||||
for field in ['volumes', 'devices']:
|
for field in ['volumes', 'devices']:
|
||||||
md.merge_field(field, merge_path_mappings)
|
md.merge_field(field, merge_path_mappings)
|
||||||
|
|
||||||
for field in [
|
for field in [
|
||||||
'ports', 'cap_add', 'cap_drop', 'expose', 'external_links',
|
'ports', 'cap_add', 'cap_drop', 'expose', 'external_links',
|
||||||
'security_opt', 'volumes_from',
|
'security_opt', 'volumes_from', 'depends_on',
|
||||||
]:
|
]:
|
||||||
md.merge_field(field, merge_unique_items_lists, default=[])
|
md.merge_field(field, merge_unique_items_lists, default=[])
|
||||||
|
|
||||||
|
|
@ -960,9 +920,6 @@ parse_environment = functools.partial(parse_dict_or_list, split_env, 'environmen
|
||||||
parse_labels = functools.partial(parse_dict_or_list, split_kv, 'labels')
|
parse_labels = functools.partial(parse_dict_or_list, split_kv, 'labels')
|
||||||
parse_networks = functools.partial(parse_dict_or_list, lambda k: (k, None), 'networks')
|
parse_networks = functools.partial(parse_dict_or_list, lambda k: (k, None), 'networks')
|
||||||
parse_sysctls = functools.partial(parse_dict_or_list, split_kv, 'sysctls')
|
parse_sysctls = functools.partial(parse_dict_or_list, split_kv, 'sysctls')
|
||||||
parse_depends_on = functools.partial(
|
|
||||||
parse_dict_or_list, lambda k: (k, {'condition': 'service_started'}), 'depends_on'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_ulimits(ulimits):
|
def parse_ulimits(ulimits):
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,6 @@
|
||||||
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||||
"shm_size": {"type": ["number", "string"]},
|
"shm_size": {"type": ["number", "string"]},
|
||||||
"stdin_open": {"type": "boolean"},
|
"stdin_open": {"type": "boolean"},
|
||||||
"stop_grace_period": {"type": "string", "format": "duration"},
|
|
||||||
"stop_signal": {"type": "string"},
|
"stop_signal": {"type": "string"},
|
||||||
"tmpfs": {"$ref": "#/definitions/string_or_list"},
|
"tmpfs": {"$ref": "#/definitions/string_or_list"},
|
||||||
"tty": {"type": "boolean"},
|
"tty": {"type": "boolean"},
|
||||||
|
|
@ -276,9 +275,9 @@
|
||||||
"type": ["boolean", "object"],
|
"type": ["boolean", "object"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {"type": "string"}
|
"name": {"type": "string"}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,6 @@
|
||||||
"shm_size": {"type": ["number", "string"]},
|
"shm_size": {"type": ["number", "string"]},
|
||||||
"sysctls": {"$ref": "#/definitions/list_or_dict"},
|
"sysctls": {"$ref": "#/definitions/list_or_dict"},
|
||||||
"stdin_open": {"type": "boolean"},
|
"stdin_open": {"type": "boolean"},
|
||||||
"stop_grace_period": {"type": "string", "format": "duration"},
|
|
||||||
"stop_signal": {"type": "string"},
|
"stop_signal": {"type": "string"},
|
||||||
"tmpfs": {"$ref": "#/definitions/string_or_list"},
|
"tmpfs": {"$ref": "#/definitions/string_or_list"},
|
||||||
"tty": {"type": "boolean"},
|
"tty": {"type": "boolean"},
|
||||||
|
|
@ -322,11 +321,11 @@
|
||||||
"type": ["boolean", "object"],
|
"type": ["boolean", "object"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {"type": "string"}
|
"name": {"type": "string"}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
"labels": {"$ref": "#/definitions/list_or_dict"},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"labels": {"$ref": "#/definitions/list_or_dict"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -169,8 +169,8 @@
|
||||||
"shm_size": {"type": ["number", "string"]},
|
"shm_size": {"type": ["number", "string"]},
|
||||||
"sysctls": {"$ref": "#/definitions/list_or_dict"},
|
"sysctls": {"$ref": "#/definitions/list_or_dict"},
|
||||||
"stdin_open": {"type": "boolean"},
|
"stdin_open": {"type": "boolean"},
|
||||||
"stop_grace_period": {"type": "string", "format": "duration"},
|
|
||||||
"stop_signal": {"type": "string"},
|
"stop_signal": {"type": "string"},
|
||||||
|
"stop_grace_period": {"type": "string", "format": "duration"},
|
||||||
"tmpfs": {"$ref": "#/definitions/string_or_list"},
|
"tmpfs": {"$ref": "#/definitions/string_or_list"},
|
||||||
"tty": {"type": "boolean"},
|
"tty": {"type": "boolean"},
|
||||||
"ulimits": {
|
"ulimits": {
|
||||||
|
|
@ -270,7 +270,7 @@
|
||||||
"cpus": {"type": "string"},
|
"cpus": {"type": "string"},
|
||||||
"memory": {"type": "string"}
|
"memory": {"type": "string"}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionaProperties": false
|
||||||
},
|
},
|
||||||
|
|
||||||
"network": {
|
"network": {
|
||||||
|
|
@ -308,7 +308,6 @@
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
"internal": {"type": "boolean"},
|
|
||||||
"labels": {"$ref": "#/definitions/list_or_dict"}
|
"labels": {"$ref": "#/definitions/list_or_dict"}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
|
|
@ -329,11 +328,10 @@
|
||||||
"type": ["boolean", "object"],
|
"type": ["boolean", "object"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {"type": "string"}
|
"name": {"type": "string"}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": false
|
"labels": {"$ref": "#/definitions/list_or_dict"},
|
||||||
},
|
|
||||||
"labels": {"$ref": "#/definitions/list_or_dict"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
"additionalProperties": false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,428 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
|
||||||
"id": "config_schema_v3.1.json",
|
|
||||||
"type": "object",
|
|
||||||
"required": ["version"],
|
|
||||||
|
|
||||||
"properties": {
|
|
||||||
"version": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
|
|
||||||
"services": {
|
|
||||||
"id": "#/properties/services",
|
|
||||||
"type": "object",
|
|
||||||
"patternProperties": {
|
|
||||||
"^[a-zA-Z0-9._-]+$": {
|
|
||||||
"$ref": "#/definitions/service"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"networks": {
|
|
||||||
"id": "#/properties/networks",
|
|
||||||
"type": "object",
|
|
||||||
"patternProperties": {
|
|
||||||
"^[a-zA-Z0-9._-]+$": {
|
|
||||||
"$ref": "#/definitions/network"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"volumes": {
|
|
||||||
"id": "#/properties/volumes",
|
|
||||||
"type": "object",
|
|
||||||
"patternProperties": {
|
|
||||||
"^[a-zA-Z0-9._-]+$": {
|
|
||||||
"$ref": "#/definitions/volume"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"secrets": {
|
|
||||||
"id": "#/properties/secrets",
|
|
||||||
"type": "object",
|
|
||||||
"patternProperties": {
|
|
||||||
"^[a-zA-Z0-9._-]+$": {
|
|
||||||
"$ref": "#/definitions/secret"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
"additionalProperties": false,
|
|
||||||
|
|
||||||
"definitions": {
|
|
||||||
|
|
||||||
"service": {
|
|
||||||
"id": "#/definitions/service",
|
|
||||||
"type": "object",
|
|
||||||
|
|
||||||
"properties": {
|
|
||||||
"deploy": {"$ref": "#/definitions/deployment"},
|
|
||||||
"build": {
|
|
||||||
"oneOf": [
|
|
||||||
{"type": "string"},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"context": {"type": "string"},
|
|
||||||
"dockerfile": {"type": "string"},
|
|
||||||
"args": {"$ref": "#/definitions/list_or_dict"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
|
||||||
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
|
||||||
"cgroup_parent": {"type": "string"},
|
|
||||||
"command": {
|
|
||||||
"oneOf": [
|
|
||||||
{"type": "string"},
|
|
||||||
{"type": "array", "items": {"type": "string"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"container_name": {"type": "string"},
|
|
||||||
"depends_on": {"$ref": "#/definitions/list_of_strings"},
|
|
||||||
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
|
||||||
"dns": {"$ref": "#/definitions/string_or_list"},
|
|
||||||
"dns_search": {"$ref": "#/definitions/string_or_list"},
|
|
||||||
"domainname": {"type": "string"},
|
|
||||||
"entrypoint": {
|
|
||||||
"oneOf": [
|
|
||||||
{"type": "string"},
|
|
||||||
{"type": "array", "items": {"type": "string"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"env_file": {"$ref": "#/definitions/string_or_list"},
|
|
||||||
"environment": {"$ref": "#/definitions/list_or_dict"},
|
|
||||||
|
|
||||||
"expose": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": ["string", "number"],
|
|
||||||
"format": "expose"
|
|
||||||
},
|
|
||||||
"uniqueItems": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
|
||||||
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
|
|
||||||
"healthcheck": {"$ref": "#/definitions/healthcheck"},
|
|
||||||
"hostname": {"type": "string"},
|
|
||||||
"image": {"type": "string"},
|
|
||||||
"ipc": {"type": "string"},
|
|
||||||
"labels": {"$ref": "#/definitions/list_or_dict"},
|
|
||||||
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
|
||||||
|
|
||||||
"logging": {
|
|
||||||
"type": "object",
|
|
||||||
|
|
||||||
"properties": {
|
|
||||||
"driver": {"type": "string"},
|
|
||||||
"options": {
|
|
||||||
"type": "object",
|
|
||||||
"patternProperties": {
|
|
||||||
"^.+$": {"type": ["string", "number", "null"]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"mac_address": {"type": "string"},
|
|
||||||
"network_mode": {"type": "string"},
|
|
||||||
|
|
||||||
"networks": {
|
|
||||||
"oneOf": [
|
|
||||||
{"$ref": "#/definitions/list_of_strings"},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"patternProperties": {
|
|
||||||
"^[a-zA-Z0-9._-]+$": {
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"aliases": {"$ref": "#/definitions/list_of_strings"},
|
|
||||||
"ipv4_address": {"type": "string"},
|
|
||||||
"ipv6_address": {"type": "string"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
{"type": "null"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"pid": {"type": ["string", "null"]},
|
|
||||||
|
|
||||||
"ports": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": ["string", "number"],
|
|
||||||
"format": "ports"
|
|
||||||
},
|
|
||||||
"uniqueItems": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"privileged": {"type": "boolean"},
|
|
||||||
"read_only": {"type": "boolean"},
|
|
||||||
"restart": {"type": "string"},
|
|
||||||
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
|
||||||
"shm_size": {"type": ["number", "string"]},
|
|
||||||
"secrets": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"oneOf": [
|
|
||||||
{"type": "string"},
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"source": {"type": "string"},
|
|
||||||
"target": {"type": "string"},
|
|
||||||
"uid": {"type": "string"},
|
|
||||||
"gid": {"type": "string"},
|
|
||||||
"mode": {"type": "number"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sysctls": {"$ref": "#/definitions/list_or_dict"},
|
|
||||||
"stdin_open": {"type": "boolean"},
|
|
||||||
"stop_grace_period": {"type": "string", "format": "duration"},
|
|
||||||
"stop_signal": {"type": "string"},
|
|
||||||
"tmpfs": {"$ref": "#/definitions/string_or_list"},
|
|
||||||
"tty": {"type": "boolean"},
|
|
||||||
"ulimits": {
|
|
||||||
"type": "object",
|
|
||||||
"patternProperties": {
|
|
||||||
"^[a-z]+$": {
|
|
||||||
"oneOf": [
|
|
||||||
{"type": "integer"},
|
|
||||||
{
|
|
||||||
"type":"object",
|
|
||||||
"properties": {
|
|
||||||
"hard": {"type": "integer"},
|
|
||||||
"soft": {"type": "integer"}
|
|
||||||
},
|
|
||||||
"required": ["soft", "hard"],
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"user": {"type": "string"},
|
|
||||||
"userns_mode": {"type": "string"},
|
|
||||||
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
|
||||||
"working_dir": {"type": "string"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"healthcheck": {
|
|
||||||
"id": "#/definitions/healthcheck",
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": false,
|
|
||||||
"properties": {
|
|
||||||
"disable": {"type": "boolean"},
|
|
||||||
"interval": {"type": "string"},
|
|
||||||
"retries": {"type": "number"},
|
|
||||||
"test": {
|
|
||||||
"oneOf": [
|
|
||||||
{"type": "string"},
|
|
||||||
{"type": "array", "items": {"type": "string"}}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"timeout": {"type": "string"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"deployment": {
|
|
||||||
"id": "#/definitions/deployment",
|
|
||||||
"type": ["object", "null"],
|
|
||||||
"properties": {
|
|
||||||
"mode": {"type": "string"},
|
|
||||||
"replicas": {"type": "integer"},
|
|
||||||
"labels": {"$ref": "#/definitions/list_or_dict"},
|
|
||||||
"update_config": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"parallelism": {"type": "integer"},
|
|
||||||
"delay": {"type": "string", "format": "duration"},
|
|
||||||
"failure_action": {"type": "string"},
|
|
||||||
"monitor": {"type": "string", "format": "duration"},
|
|
||||||
"max_failure_ratio": {"type": "number"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"resources": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"limits": {"$ref": "#/definitions/resource"},
|
|
||||||
"reservations": {"$ref": "#/definitions/resource"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"restart_policy": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"condition": {"type": "string"},
|
|
||||||
"delay": {"type": "string", "format": "duration"},
|
|
||||||
"max_attempts": {"type": "integer"},
|
|
||||||
"window": {"type": "string", "format": "duration"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"placement": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"constraints": {"type": "array", "items": {"type": "string"}}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"resource": {
|
|
||||||
"id": "#/definitions/resource",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"cpus": {"type": "string"},
|
|
||||||
"memory": {"type": "string"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"network": {
|
|
||||||
"id": "#/definitions/network",
|
|
||||||
"type": ["object", "null"],
|
|
||||||
"properties": {
|
|
||||||
"driver": {"type": "string"},
|
|
||||||
"driver_opts": {
|
|
||||||
"type": "object",
|
|
||||||
"patternProperties": {
|
|
||||||
"^.+$": {"type": ["string", "number"]}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ipam": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"driver": {"type": "string"},
|
|
||||||
"config": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"subnet": {"type": "string"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"external": {
|
|
||||||
"type": ["boolean", "object"],
|
|
||||||
"properties": {
|
|
||||||
"name": {"type": "string"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"internal": {"type": "boolean"},
|
|
||||||
"labels": {"$ref": "#/definitions/list_or_dict"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"volume": {
|
|
||||||
"id": "#/definitions/volume",
|
|
||||||
"type": ["object", "null"],
|
|
||||||
"properties": {
|
|
||||||
"driver": {"type": "string"},
|
|
||||||
"driver_opts": {
|
|
||||||
"type": "object",
|
|
||||||
"patternProperties": {
|
|
||||||
"^.+$": {"type": ["string", "number"]}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"external": {
|
|
||||||
"type": ["boolean", "object"],
|
|
||||||
"properties": {
|
|
||||||
"name": {"type": "string"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
"labels": {"$ref": "#/definitions/list_or_dict"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"secret": {
|
|
||||||
"id": "#/definitions/secret",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"file": {"type": "string"},
|
|
||||||
"external": {
|
|
||||||
"type": ["boolean", "object"],
|
|
||||||
"properties": {
|
|
||||||
"name": {"type": "string"}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"labels": {"$ref": "#/definitions/list_or_dict"}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"string_or_list": {
|
|
||||||
"oneOf": [
|
|
||||||
{"type": "string"},
|
|
||||||
{"$ref": "#/definitions/list_of_strings"}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"list_of_strings": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {"type": "string"},
|
|
||||||
"uniqueItems": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"list_or_dict": {
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"type": "object",
|
|
||||||
"patternProperties": {
|
|
||||||
".+": {
|
|
||||||
"type": ["string", "number", "null"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false
|
|
||||||
},
|
|
||||||
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
|
|
||||||
"constraints": {
|
|
||||||
"service": {
|
|
||||||
"id": "#/definitions/constraints/service",
|
|
||||||
"anyOf": [
|
|
||||||
{"required": ["build"]},
|
|
||||||
{"required": ["image"]}
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"build": {
|
|
||||||
"required": ["context"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -2,7 +2,6 @@ from __future__ import absolute_import
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
import contextlib
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
@ -32,8 +31,7 @@ def env_vars_from_file(filename):
|
||||||
elif not os.path.isfile(filename):
|
elif not os.path.isfile(filename):
|
||||||
raise ConfigurationError("%s is not a file." % (filename))
|
raise ConfigurationError("%s is not a file." % (filename))
|
||||||
env = {}
|
env = {}
|
||||||
with contextlib.closing(codecs.open(filename, 'r', 'utf-8')) as fileobj:
|
for line in codecs.open(filename, 'r', 'utf-8'):
|
||||||
for line in fileobj:
|
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line and not line.startswith('#'):
|
if line and not line.startswith('#'):
|
||||||
k, v = split_env(line)
|
k, v = split_env(line)
|
||||||
|
|
@ -107,14 +105,3 @@ class Environment(dict):
|
||||||
super(Environment, self).get(key.upper(), *args, **kwargs)
|
super(Environment, self).get(key.upper(), *args, **kwargs)
|
||||||
)
|
)
|
||||||
return super(Environment, self).get(key, *args, **kwargs)
|
return super(Environment, self).get(key, *args, **kwargs)
|
||||||
|
|
||||||
def get_boolean(self, key):
|
|
||||||
# Convert a value to a boolean using "common sense" rules.
|
|
||||||
# Unset, empty, "0" and "false" (i-case) yield False.
|
|
||||||
# All other values yield True.
|
|
||||||
value = self.get(key)
|
|
||||||
if not value:
|
|
||||||
return False
|
|
||||||
if value.lower() in ['0', 'false']:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
|
||||||
|
|
@ -32,11 +32,6 @@ def denormalize_config(config):
|
||||||
if 'external_name' in net_conf:
|
if 'external_name' in net_conf:
|
||||||
del net_conf['external_name']
|
del net_conf['external_name']
|
||||||
|
|
||||||
volumes = config.volumes.copy()
|
|
||||||
for vol_name, vol_conf in volumes.items():
|
|
||||||
if 'external_name' in vol_conf:
|
|
||||||
del vol_conf['external_name']
|
|
||||||
|
|
||||||
version = config.version
|
version = config.version
|
||||||
if version == V1:
|
if version == V1:
|
||||||
version = V2_1
|
version = V2_1
|
||||||
|
|
@ -45,7 +40,7 @@ def denormalize_config(config):
|
||||||
'version': version,
|
'version': version,
|
||||||
'services': services,
|
'services': services,
|
||||||
'networks': networks,
|
'networks': networks,
|
||||||
'volumes': volumes,
|
'volumes': config.volumes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -57,52 +52,13 @@ def serialize_config(config):
|
||||||
width=80)
|
width=80)
|
||||||
|
|
||||||
|
|
||||||
def serialize_ns_time_value(value):
|
|
||||||
result = (value, 'ns')
|
|
||||||
table = [
|
|
||||||
(1000., 'us'),
|
|
||||||
(1000., 'ms'),
|
|
||||||
(1000., 's'),
|
|
||||||
(60., 'm'),
|
|
||||||
(60., 'h')
|
|
||||||
]
|
|
||||||
for stage in table:
|
|
||||||
tmp = value / stage[0]
|
|
||||||
if tmp == int(value / stage[0]):
|
|
||||||
value = tmp
|
|
||||||
result = (int(value), stage[1])
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return '{0}{1}'.format(*result)
|
|
||||||
|
|
||||||
|
|
||||||
def denormalize_service_dict(service_dict, version):
|
def denormalize_service_dict(service_dict, version):
|
||||||
service_dict = service_dict.copy()
|
service_dict = service_dict.copy()
|
||||||
|
|
||||||
if 'restart' in service_dict:
|
if 'restart' in service_dict:
|
||||||
service_dict['restart'] = types.serialize_restart_spec(
|
service_dict['restart'] = types.serialize_restart_spec(service_dict['restart'])
|
||||||
service_dict['restart']
|
|
||||||
)
|
|
||||||
|
|
||||||
if version == V1 and 'network_mode' not in service_dict:
|
if version == V1 and 'network_mode' not in service_dict:
|
||||||
service_dict['network_mode'] = 'bridge'
|
service_dict['network_mode'] = 'bridge'
|
||||||
|
|
||||||
if 'depends_on' in service_dict and version != V2_1:
|
|
||||||
service_dict['depends_on'] = sorted([
|
|
||||||
svc for svc in service_dict['depends_on'].keys()
|
|
||||||
])
|
|
||||||
|
|
||||||
if 'healthcheck' in service_dict:
|
|
||||||
if 'interval' in service_dict['healthcheck']:
|
|
||||||
service_dict['healthcheck']['interval'] = serialize_ns_time_value(
|
|
||||||
service_dict['healthcheck']['interval']
|
|
||||||
)
|
|
||||||
if 'timeout' in service_dict['healthcheck']:
|
|
||||||
service_dict['healthcheck']['timeout'] = serialize_ns_time_value(
|
|
||||||
service_dict['healthcheck']['timeout']
|
|
||||||
)
|
|
||||||
|
|
||||||
if 'secrets' in service_dict:
|
|
||||||
service_dict['secrets'] = map(lambda s: s.repr(), service_dict['secrets'])
|
|
||||||
|
|
||||||
return service_dict
|
return service_dict
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ from collections import namedtuple
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from ..const import COMPOSEFILE_V1 as V1
|
from compose.config.config import V1
|
||||||
from .errors import ConfigurationError
|
from compose.config.errors import ConfigurationError
|
||||||
from compose.const import IS_WINDOWS_PLATFORM
|
from compose.const import IS_WINDOWS_PLATFORM
|
||||||
from compose.utils import splitdrive
|
from compose.utils import splitdrive
|
||||||
|
|
||||||
|
|
@ -234,27 +234,3 @@ class ServiceLink(namedtuple('_ServiceLink', 'target alias')):
|
||||||
@property
|
@property
|
||||||
def merge_field(self):
|
def merge_field(self):
|
||||||
return self.alias
|
return self.alias
|
||||||
|
|
||||||
|
|
||||||
class ServiceSecret(namedtuple('_ServiceSecret', 'source target uid gid mode')):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def parse(cls, spec):
|
|
||||||
if isinstance(spec, six.string_types):
|
|
||||||
return cls(spec, None, None, None, None)
|
|
||||||
return cls(
|
|
||||||
spec.get('source'),
|
|
||||||
spec.get('target'),
|
|
||||||
spec.get('uid'),
|
|
||||||
spec.get('gid'),
|
|
||||||
spec.get('mode'),
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def merge_field(self):
|
|
||||||
return self.source
|
|
||||||
|
|
||||||
def repr(self):
|
|
||||||
return dict(
|
|
||||||
[(k, v) for k, v in self._asdict().items() if v is not None]
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import sys
|
||||||
|
|
||||||
DEFAULT_TIMEOUT = 10
|
DEFAULT_TIMEOUT = 10
|
||||||
HTTP_TIMEOUT = 60
|
HTTP_TIMEOUT = 60
|
||||||
IMAGE_EVENTS = ['delete', 'import', 'load', 'pull', 'push', 'save', 'tag', 'untag']
|
IMAGE_EVENTS = ['delete', 'import', 'pull', 'push', 'tag', 'untag']
|
||||||
IS_WINDOWS_PLATFORM = (sys.platform == "win32")
|
IS_WINDOWS_PLATFORM = (sys.platform == "win32")
|
||||||
LABEL_CONTAINER_NUMBER = 'com.docker.compose.container-number'
|
LABEL_CONTAINER_NUMBER = 'com.docker.compose.container-number'
|
||||||
LABEL_ONE_OFF = 'com.docker.compose.oneoff'
|
LABEL_ONE_OFF = 'com.docker.compose.oneoff'
|
||||||
|
|
@ -16,20 +16,16 @@ LABEL_VERSION = 'com.docker.compose.version'
|
||||||
LABEL_VOLUME = 'com.docker.compose.volume'
|
LABEL_VOLUME = 'com.docker.compose.volume'
|
||||||
LABEL_CONFIG_HASH = 'com.docker.compose.config-hash'
|
LABEL_CONFIG_HASH = 'com.docker.compose.config-hash'
|
||||||
|
|
||||||
SECRETS_PATH = '/run/secrets'
|
|
||||||
|
|
||||||
COMPOSEFILE_V1 = '1'
|
COMPOSEFILE_V1 = '1'
|
||||||
COMPOSEFILE_V2_0 = '2.0'
|
COMPOSEFILE_V2_0 = '2.0'
|
||||||
COMPOSEFILE_V2_1 = '2.1'
|
COMPOSEFILE_V2_1 = '2.1'
|
||||||
COMPOSEFILE_V3_0 = '3.0'
|
COMPOSEFILE_V3_0 = '3.0'
|
||||||
COMPOSEFILE_V3_1 = '3.1'
|
|
||||||
|
|
||||||
API_VERSIONS = {
|
API_VERSIONS = {
|
||||||
COMPOSEFILE_V1: '1.21',
|
COMPOSEFILE_V1: '1.21',
|
||||||
COMPOSEFILE_V2_0: '1.22',
|
COMPOSEFILE_V2_0: '1.22',
|
||||||
COMPOSEFILE_V2_1: '1.24',
|
COMPOSEFILE_V2_1: '1.24',
|
||||||
COMPOSEFILE_V3_0: '1.25',
|
COMPOSEFILE_V3_0: '1.25',
|
||||||
COMPOSEFILE_V3_1: '1.25',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
API_VERSION_TO_ENGINE_VERSION = {
|
API_VERSION_TO_ENGINE_VERSION = {
|
||||||
|
|
@ -37,5 +33,4 @@ API_VERSION_TO_ENGINE_VERSION = {
|
||||||
API_VERSIONS[COMPOSEFILE_V2_0]: '1.10.0',
|
API_VERSIONS[COMPOSEFILE_V2_0]: '1.10.0',
|
||||||
API_VERSIONS[COMPOSEFILE_V2_1]: '1.12.0',
|
API_VERSIONS[COMPOSEFILE_V2_1]: '1.12.0',
|
||||||
API_VERSIONS[COMPOSEFILE_V3_0]: '1.13.0',
|
API_VERSIONS[COMPOSEFILE_V3_0]: '1.13.0',
|
||||||
API_VERSIONS[COMPOSEFILE_V3_1]: '1.13.0',
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,6 @@ from six.moves.queue import Empty
|
||||||
from six.moves.queue import Queue
|
from six.moves.queue import Queue
|
||||||
|
|
||||||
from compose.cli.signals import ShutdownException
|
from compose.cli.signals import ShutdownException
|
||||||
from compose.errors import HealthCheckFailed
|
|
||||||
from compose.errors import NoHealthCheckConfigured
|
|
||||||
from compose.errors import OperationFailedError
|
from compose.errors import OperationFailedError
|
||||||
from compose.utils import get_output_stream
|
from compose.utils import get_output_stream
|
||||||
|
|
||||||
|
|
@ -50,7 +48,7 @@ def parallel_execute(objects, func, get_name, msg, get_deps=None):
|
||||||
elif isinstance(exception, APIError):
|
elif isinstance(exception, APIError):
|
||||||
errors[get_name(obj)] = exception.explanation
|
errors[get_name(obj)] = exception.explanation
|
||||||
writer.write(get_name(obj), 'error')
|
writer.write(get_name(obj), 'error')
|
||||||
elif isinstance(exception, (OperationFailedError, HealthCheckFailed, NoHealthCheckConfigured)):
|
elif isinstance(exception, OperationFailedError):
|
||||||
errors[get_name(obj)] = exception.msg
|
errors[get_name(obj)] = exception.msg
|
||||||
writer.write(get_name(obj), 'error')
|
writer.write(get_name(obj), 'error')
|
||||||
elif isinstance(exception, UpstreamError):
|
elif isinstance(exception, UpstreamError):
|
||||||
|
|
@ -166,7 +164,7 @@ def feed_queue(objects, func, get_deps, results, state):
|
||||||
|
|
||||||
for obj in pending:
|
for obj in pending:
|
||||||
deps = get_deps(obj)
|
deps = get_deps(obj)
|
||||||
try:
|
|
||||||
if any(dep[0] in state.failed for dep in deps):
|
if any(dep[0] in state.failed for dep in deps):
|
||||||
log.debug('{} has upstream errors - not processing'.format(obj))
|
log.debug('{} has upstream errors - not processing'.format(obj))
|
||||||
results.put((obj, None, UpstreamError()))
|
results.put((obj, None, UpstreamError()))
|
||||||
|
|
@ -181,12 +179,6 @@ def feed_queue(objects, func, get_deps, results, state):
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
state.started.add(obj)
|
state.started.add(obj)
|
||||||
except (HealthCheckFailed, NoHealthCheckConfigured) as e:
|
|
||||||
log.debug(
|
|
||||||
'Healthcheck for service(s) upstream of {} failed - '
|
|
||||||
'not processing'.format(obj)
|
|
||||||
)
|
|
||||||
results.put((obj, None, e))
|
|
||||||
|
|
||||||
if state.is_done():
|
if state.is_done():
|
||||||
results.put(STOP)
|
results.put(STOP)
|
||||||
|
|
|
||||||
|
|
@ -104,11 +104,6 @@ class Project(object):
|
||||||
for volume_spec in service_dict.get('volumes', [])
|
for volume_spec in service_dict.get('volumes', [])
|
||||||
]
|
]
|
||||||
|
|
||||||
secrets = get_secrets(
|
|
||||||
service_dict['name'],
|
|
||||||
service_dict.pop('secrets', None) or [],
|
|
||||||
config_data.secrets)
|
|
||||||
|
|
||||||
project.services.append(
|
project.services.append(
|
||||||
Service(
|
Service(
|
||||||
service_dict.pop('name'),
|
service_dict.pop('name'),
|
||||||
|
|
@ -119,7 +114,6 @@ class Project(object):
|
||||||
links=links,
|
links=links,
|
||||||
network_mode=network_mode,
|
network_mode=network_mode,
|
||||||
volumes_from=volumes_from,
|
volumes_from=volumes_from,
|
||||||
secrets=secrets,
|
|
||||||
**service_dict)
|
**service_dict)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -365,7 +359,7 @@ class Project(object):
|
||||||
|
|
||||||
# TODO: get labels from the API v1.22 , see github issue 2618
|
# TODO: get labels from the API v1.22 , see github issue 2618
|
||||||
try:
|
try:
|
||||||
# this can fail if the container has been removed
|
# this can fail if the conatiner has been removed
|
||||||
container = Container.from_id(self.client, event['id'])
|
container = Container.from_id(self.client, event['id'])
|
||||||
except APIError:
|
except APIError:
|
||||||
continue
|
continue
|
||||||
|
|
@ -559,33 +553,6 @@ def get_volumes_from(project, service_dict):
|
||||||
return [build_volume_from(vf) for vf in volumes_from]
|
return [build_volume_from(vf) for vf in volumes_from]
|
||||||
|
|
||||||
|
|
||||||
def get_secrets(service, service_secrets, secret_defs):
|
|
||||||
secrets = []
|
|
||||||
|
|
||||||
for secret in service_secrets:
|
|
||||||
secret_def = secret_defs.get(secret.source)
|
|
||||||
if not secret_def:
|
|
||||||
raise ConfigurationError(
|
|
||||||
"Service \"{service}\" uses an undefined secret \"{secret}\" "
|
|
||||||
.format(service=service, secret=secret.source))
|
|
||||||
|
|
||||||
if secret_def.get('external_name'):
|
|
||||||
log.warn("Service \"{service}\" uses secret \"{secret}\" which is external. "
|
|
||||||
"External secrets are not available to containers created by "
|
|
||||||
"docker-compose.".format(service=service, secret=secret.source))
|
|
||||||
continue
|
|
||||||
|
|
||||||
if secret.uid or secret.gid or secret.mode:
|
|
||||||
log.warn("Service \"{service}\" uses secret \"{secret}\" with uid, "
|
|
||||||
"gid, or mode. These fields are not supported by this "
|
|
||||||
"implementation of the Compose file".format(
|
|
||||||
service=service, secret=secret.source))
|
|
||||||
|
|
||||||
secrets.append({'secret': secret, 'file': secret_def.get('file')})
|
|
||||||
|
|
||||||
return secrets
|
|
||||||
|
|
||||||
|
|
||||||
def warn_for_swarm_mode(client):
|
def warn_for_swarm_mode(client):
|
||||||
info = client.info()
|
info = client.info()
|
||||||
if info.get('Swarm', {}).get('LocalNodeState') == 'active':
|
if info.get('Swarm', {}).get('LocalNodeState') == 'active':
|
||||||
|
|
|
||||||
|
|
@ -10,20 +10,17 @@ from operator import attrgetter
|
||||||
import enum
|
import enum
|
||||||
import six
|
import six
|
||||||
from docker.errors import APIError
|
from docker.errors import APIError
|
||||||
from docker.errors import ImageNotFound
|
|
||||||
from docker.errors import NotFound
|
from docker.errors import NotFound
|
||||||
from docker.types import LogConfig
|
from docker.types import LogConfig
|
||||||
from docker.utils.ports import build_port_bindings
|
from docker.utils.ports import build_port_bindings
|
||||||
from docker.utils.ports import split_port
|
from docker.utils.ports import split_port
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from . import const
|
|
||||||
from . import progress_stream
|
from . import progress_stream
|
||||||
from .config import DOCKER_CONFIG_KEYS
|
from .config import DOCKER_CONFIG_KEYS
|
||||||
from .config import merge_environment
|
from .config import merge_environment
|
||||||
from .config.types import VolumeSpec
|
from .config.types import VolumeSpec
|
||||||
from .const import DEFAULT_TIMEOUT
|
from .const import DEFAULT_TIMEOUT
|
||||||
from .const import IS_WINDOWS_PLATFORM
|
|
||||||
from .const import LABEL_CONFIG_HASH
|
from .const import LABEL_CONFIG_HASH
|
||||||
from .const import LABEL_CONTAINER_NUMBER
|
from .const import LABEL_CONTAINER_NUMBER
|
||||||
from .const import LABEL_ONE_OFF
|
from .const import LABEL_ONE_OFF
|
||||||
|
|
@ -140,7 +137,6 @@ class Service(object):
|
||||||
volumes_from=None,
|
volumes_from=None,
|
||||||
network_mode=None,
|
network_mode=None,
|
||||||
networks=None,
|
networks=None,
|
||||||
secrets=None,
|
|
||||||
**options
|
**options
|
||||||
):
|
):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
@ -151,7 +147,6 @@ class Service(object):
|
||||||
self.volumes_from = volumes_from or []
|
self.volumes_from = volumes_from or []
|
||||||
self.network_mode = network_mode or NetworkMode(None)
|
self.network_mode = network_mode or NetworkMode(None)
|
||||||
self.networks = networks or {}
|
self.networks = networks or {}
|
||||||
self.secrets = secrets or []
|
|
||||||
self.options = options
|
self.options = options
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
|
|
@ -328,8 +323,11 @@ class Service(object):
|
||||||
def image(self):
|
def image(self):
|
||||||
try:
|
try:
|
||||||
return self.client.inspect_image(self.image_name)
|
return self.client.inspect_image(self.image_name)
|
||||||
except ImageNotFound:
|
except APIError as e:
|
||||||
|
if e.response.status_code == 404 and e.explanation and 'No such image' in str(e.explanation):
|
||||||
raise NoSuchImageError("Image '{}' not found".format(self.image_name))
|
raise NoSuchImageError("Image '{}' not found".format(self.image_name))
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image_name(self):
|
def image_name(self):
|
||||||
|
|
@ -695,14 +693,9 @@ class Service(object):
|
||||||
override_options['binds'] = binds
|
override_options['binds'] = binds
|
||||||
container_options['environment'].update(affinity)
|
container_options['environment'].update(affinity)
|
||||||
|
|
||||||
|
if 'volumes' in container_options:
|
||||||
container_options['volumes'] = dict(
|
container_options['volumes'] = dict(
|
||||||
(v.internal, {}) for v in container_options.get('volumes') or {})
|
(v.internal, {}) for v in container_options['volumes'])
|
||||||
|
|
||||||
secret_volumes = self.get_secret_volumes()
|
|
||||||
if secret_volumes:
|
|
||||||
override_options['binds'].extend(v.repr() for v in secret_volumes)
|
|
||||||
container_options['volumes'].update(
|
|
||||||
(v.internal, {}) for v in secret_volumes)
|
|
||||||
|
|
||||||
container_options['image'] = self.image_name
|
container_options['image'] = self.image_name
|
||||||
|
|
||||||
|
|
@ -773,23 +766,14 @@ class Service(object):
|
||||||
|
|
||||||
return host_config
|
return host_config
|
||||||
|
|
||||||
def get_secret_volumes(self):
|
|
||||||
def build_spec(secret):
|
|
||||||
target = '{}/{}'.format(
|
|
||||||
const.SECRETS_PATH,
|
|
||||||
secret['secret'].target or secret['secret'].source)
|
|
||||||
return VolumeSpec(secret['file'], target, 'ro')
|
|
||||||
|
|
||||||
return [build_spec(secret) for secret in self.secrets]
|
|
||||||
|
|
||||||
def build(self, no_cache=False, pull=False, force_rm=False):
|
def build(self, no_cache=False, pull=False, force_rm=False):
|
||||||
log.info('Building %s' % self.name)
|
log.info('Building %s' % self.name)
|
||||||
|
|
||||||
build_opts = self.options.get('build', {})
|
build_opts = self.options.get('build', {})
|
||||||
path = build_opts.get('context')
|
path = build_opts.get('context')
|
||||||
# python2 os.stat() doesn't support unicode on some UNIX, so we
|
# python2 os.path() doesn't support unicode, so we need to encode it to
|
||||||
# encode it to a bytestring to be safe
|
# a byte string
|
||||||
if not six.PY3 and not IS_WINDOWS_PLATFORM:
|
if not six.PY3:
|
||||||
path = path.encode('utf8')
|
path = path.encode('utf8')
|
||||||
|
|
||||||
build_output = self.client.build(
|
build_output = self.client.build(
|
||||||
|
|
|
||||||
|
|
@ -434,18 +434,6 @@ _docker_compose_stop() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_docker_compose_top() {
|
|
||||||
case "$cur" in
|
|
||||||
-*)
|
|
||||||
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) )
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
__docker_compose_services_running
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
_docker_compose_unpause() {
|
_docker_compose_unpause() {
|
||||||
case "$cur" in
|
case "$cur" in
|
||||||
-*)
|
-*)
|
||||||
|
|
@ -511,7 +499,6 @@ _docker_compose() {
|
||||||
scale
|
scale
|
||||||
start
|
start
|
||||||
stop
|
stop
|
||||||
top
|
|
||||||
unpause
|
unpause
|
||||||
up
|
up
|
||||||
version
|
version
|
||||||
|
|
|
||||||
|
|
@ -341,11 +341,6 @@ __docker-compose_subcommand() {
|
||||||
$opts_timeout \
|
$opts_timeout \
|
||||||
'*:running services:__docker-compose_runningservices' && ret=0
|
'*:running services:__docker-compose_runningservices' && ret=0
|
||||||
;;
|
;;
|
||||||
(top)
|
|
||||||
_arguments \
|
|
||||||
$opts_help \
|
|
||||||
'*:running services:__docker-compose_runningservices' && ret=0
|
|
||||||
;;
|
|
||||||
(unpause)
|
(unpause)
|
||||||
_arguments \
|
_arguments \
|
||||||
$opts_help \
|
$opts_help \
|
||||||
|
|
@ -391,17 +386,9 @@ _docker-compose() {
|
||||||
integer ret=1
|
integer ret=1
|
||||||
typeset -A opt_args
|
typeset -A opt_args
|
||||||
|
|
||||||
local file_description
|
|
||||||
|
|
||||||
if [[ -n ${words[(r)-f]} || -n ${words[(r)--file]} ]] ; then
|
|
||||||
file_description="Specify an override docker-compose file (default: docker-compose.override.yml)"
|
|
||||||
else
|
|
||||||
file_description="Specify an alternate docker-compose file (default: docker-compose.yml)"
|
|
||||||
fi
|
|
||||||
|
|
||||||
_arguments -C \
|
_arguments -C \
|
||||||
'(- :)'{-h,--help}'[Get help]' \
|
'(- :)'{-h,--help}'[Get help]' \
|
||||||
'*'{-f,--file}"[${file_description}]:file:_files -g '*.yml'" \
|
'(-f --file)'{-f,--file}'[Specify an alternate docker-compose file (default: docker-compose.yml)]:file:_files -g "*.yml"' \
|
||||||
'(-p --project-name)'{-p,--project-name}'[Specify an alternate project name (default: directory name)]:project name:' \
|
'(-p --project-name)'{-p,--project-name}'[Specify an alternate project name (default: directory name)]:project name:' \
|
||||||
'--verbose[Show more output]' \
|
'--verbose[Show more output]' \
|
||||||
'(- :)'{-v,--version}'[Print version and exit]' \
|
'(- :)'{-v,--version}'[Print version and exit]' \
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,6 @@ exe = EXE(pyz,
|
||||||
'compose/config/config_schema_v3.0.json',
|
'compose/config/config_schema_v3.0.json',
|
||||||
'DATA'
|
'DATA'
|
||||||
),
|
),
|
||||||
(
|
|
||||||
'compose/config/config_schema_v3.1.json',
|
|
||||||
'compose/config/config_schema_v3.1.json',
|
|
||||||
'DATA'
|
|
||||||
),
|
|
||||||
(
|
(
|
||||||
'compose/GITSHA',
|
'compose/GITSHA',
|
||||||
'compose/GITSHA',
|
'compose/GITSHA',
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
pyinstaller==3.2.1
|
pyinstaller==3.1.1
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ PyYAML==3.11
|
||||||
backports.ssl-match-hostname==3.5.0.1; python_version < '3'
|
backports.ssl-match-hostname==3.5.0.1; python_version < '3'
|
||||||
cached-property==1.2.0
|
cached-property==1.2.0
|
||||||
colorama==0.3.7
|
colorama==0.3.7
|
||||||
docker==2.0.2
|
docker==2.0.0
|
||||||
dockerpty==0.4.1
|
dockerpty==0.4.1
|
||||||
docopt==0.6.1
|
docopt==0.6.1
|
||||||
enum34==1.0.4; python_version < '3.4'
|
enum34==1.0.4; python_version < '3.4'
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,6 @@ TAG=$1
|
||||||
VERSION="$(python setup.py --version)"
|
VERSION="$(python setup.py --version)"
|
||||||
|
|
||||||
./script/build/write-git-sha
|
./script/build/write-git-sha
|
||||||
python setup.py sdist bdist_wheel
|
python setup.py sdist
|
||||||
docker build --build-arg version=$VERSION -t docker/compose:$TAG -f Dockerfile.run .
|
cp dist/docker-compose-$VERSION.tar.gz dist/docker-compose-release.tar.gz
|
||||||
|
docker build -t docker/compose:$TAG -f Dockerfile.run .
|
||||||
|
|
|
||||||
|
|
@ -54,19 +54,18 @@ git push $GITHUB_REPO $VERSION
|
||||||
echo "Uploading the docker image"
|
echo "Uploading the docker image"
|
||||||
docker push docker/compose:$VERSION
|
docker push docker/compose:$VERSION
|
||||||
|
|
||||||
echo "Uploading package to PyPI"
|
echo "Uploading sdist to PyPI"
|
||||||
pandoc -f markdown -t rst README.md -o README.rst
|
pandoc -f markdown -t rst README.md -o README.rst
|
||||||
sed -i -e 's/logo.png?raw=true/https:\/\/github.com\/docker\/compose\/raw\/master\/logo.png?raw=true/' README.rst
|
sed -i -e 's/logo.png?raw=true/https:\/\/github.com\/docker\/compose\/raw\/master\/logo.png?raw=true/' README.rst
|
||||||
./script/build/write-git-sha
|
./script/build/write-git-sha
|
||||||
python setup.py sdist bdist_wheel
|
python setup.py sdist
|
||||||
if [ "$(command -v twine 2> /dev/null)" ]; then
|
if [ "$(command -v twine 2> /dev/null)" ]; then
|
||||||
twine upload ./dist/docker-compose-${VERSION/-/}.tar.gz ./dist/docker_compose-${VERSION/-/}-py2.py3-none-any.whl
|
twine upload ./dist/docker-compose-${VERSION/-/}.tar.gz
|
||||||
else
|
else
|
||||||
python setup.py upload
|
python setup.py upload
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Testing pip package"
|
echo "Testing pip package"
|
||||||
deactivate || true
|
|
||||||
virtualenv venv-test
|
virtualenv venv-test
|
||||||
source venv-test/bin/activate
|
source venv-test/bin/activate
|
||||||
pip install docker-compose==$VERSION
|
pip install docker-compose==$VERSION
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
#
|
#
|
||||||
# Util functions for release scripts
|
# Util functions for release scritps
|
||||||
#
|
#
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
VERSION="1.12.0dev"
|
VERSION="1.10.0-rc1"
|
||||||
IMAGE="docker/compose:$VERSION"
|
IMAGE="docker/compose:$VERSION"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ version tags for recent releases, or the default release.
|
||||||
|
|
||||||
The default release is the most recent non-RC version.
|
The default release is the most recent non-RC version.
|
||||||
|
|
||||||
Recent is a list of unique major.minor versions, where each is the most
|
Recent is a list of unqiue major.minor versions, where each is the most
|
||||||
recent version in the series.
|
recent version in the series.
|
||||||
|
|
||||||
For example, if the list of versions is:
|
For example, if the list of versions is:
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
[bdist_wheel]
|
|
||||||
universal=1
|
|
||||||
25
setup.py
25
setup.py
|
|
@ -1,7 +1,6 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
|
|
@ -9,7 +8,6 @@ import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
from setuptools import find_packages
|
from setuptools import find_packages
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
|
|
@ -37,7 +35,7 @@ install_requires = [
|
||||||
'requests >= 2.6.1, != 2.11.0, < 2.12',
|
'requests >= 2.6.1, != 2.11.0, < 2.12',
|
||||||
'texttable >= 0.8.1, < 0.9',
|
'texttable >= 0.8.1, < 0.9',
|
||||||
'websocket-client >= 0.32.0, < 1.0',
|
'websocket-client >= 0.32.0, < 1.0',
|
||||||
'docker >= 2.0.2, < 3.0',
|
'docker >= 2.0.0, < 3.0',
|
||||||
'dockerpty >= 0.4.1, < 0.5',
|
'dockerpty >= 0.4.1, < 0.5',
|
||||||
'six >= 1.3.0, < 2',
|
'six >= 1.3.0, < 2',
|
||||||
'jsonschema >= 2.5.1, < 3',
|
'jsonschema >= 2.5.1, < 3',
|
||||||
|
|
@ -51,25 +49,7 @@ tests_require = [
|
||||||
|
|
||||||
if sys.version_info[:2] < (3, 4):
|
if sys.version_info[:2] < (3, 4):
|
||||||
tests_require.append('mock >= 1.0.1')
|
tests_require.append('mock >= 1.0.1')
|
||||||
|
install_requires.append('enum34 >= 1.0.4, < 2')
|
||||||
extras_require = {
|
|
||||||
':python_version < "3.4"': ['enum34 >= 1.0.4, < 2'],
|
|
||||||
':python_version < "3.5"': ['backports.ssl_match_hostname >= 3.5'],
|
|
||||||
':python_version < "3.3"': ['ipaddress >= 1.0.16'],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
|
||||||
if 'bdist_wheel' not in sys.argv:
|
|
||||||
for key, value in extras_require.items():
|
|
||||||
if key.startswith(':') and pkg_resources.evaluate_marker(key[1:]):
|
|
||||||
install_requires.extend(value)
|
|
||||||
except Exception as e:
|
|
||||||
print("Failed to compute platform dependencies: {}. ".format(e) +
|
|
||||||
"All dependencies will be installed as a result.", file=sys.stderr)
|
|
||||||
for key, value in extras_require.items():
|
|
||||||
if key.startswith(':'):
|
|
||||||
install_requires.extend(value)
|
|
||||||
|
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
|
|
@ -83,7 +63,6 @@ setup(
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
test_suite='nose.collector',
|
test_suite='nose.collector',
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
extras_require=extras_require,
|
|
||||||
tests_require=tests_require,
|
tests_require=tests_require,
|
||||||
entry_points="""
|
entry_points="""
|
||||||
[console_scripts]
|
[console_scripts]
|
||||||
|
|
|
||||||
|
|
@ -262,20 +262,6 @@ class CLITestCase(DockerClientTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_config_external_volume(self):
|
|
||||||
self.base_dir = 'tests/fixtures/volumes'
|
|
||||||
result = self.dispatch(['-f', 'external-volumes.yml', 'config'])
|
|
||||||
json_result = yaml.load(result.stdout)
|
|
||||||
assert 'volumes' in json_result
|
|
||||||
assert json_result['volumes'] == {
|
|
||||||
'foo': {
|
|
||||||
'external': True
|
|
||||||
},
|
|
||||||
'bar': {
|
|
||||||
'external': {'name': 'some_bar'}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_config_v1(self):
|
def test_config_v1(self):
|
||||||
self.base_dir = 'tests/fixtures/v1-config'
|
self.base_dir = 'tests/fixtures/v1-config'
|
||||||
result = self.dispatch(['config'])
|
result = self.dispatch(['config'])
|
||||||
|
|
@ -309,13 +295,7 @@ class CLITestCase(DockerClientTestCase):
|
||||||
assert yaml.load(result.stdout) == {
|
assert yaml.load(result.stdout) == {
|
||||||
'version': '3.0',
|
'version': '3.0',
|
||||||
'networks': {},
|
'networks': {},
|
||||||
'volumes': {
|
'volumes': {},
|
||||||
'foobar': {
|
|
||||||
'labels': {
|
|
||||||
'com.docker.compose.test': 'true',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'services': {
|
'services': {
|
||||||
'web': {
|
'web': {
|
||||||
'image': 'busybox',
|
'image': 'busybox',
|
||||||
|
|
@ -353,8 +333,8 @@ class CLITestCase(DockerClientTestCase):
|
||||||
|
|
||||||
'healthcheck': {
|
'healthcheck': {
|
||||||
'test': 'cat /etc/passwd',
|
'test': 'cat /etc/passwd',
|
||||||
'interval': '10s',
|
'interval': 10000000000,
|
||||||
'timeout': '1s',
|
'timeout': 1000000000,
|
||||||
'retries': 5,
|
'retries': 5,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -1234,7 +1214,7 @@ class CLITestCase(DockerClientTestCase):
|
||||||
container = service.containers(stopped=True, one_off=OneOffFilter.only)[0]
|
container = service.containers(stopped=True, one_off=OneOffFilter.only)[0]
|
||||||
self.assertEqual(user, container.get('Config.User'))
|
self.assertEqual(user, container.get('Config.User'))
|
||||||
|
|
||||||
def test_run_service_with_environment_overridden(self):
|
def test_run_service_with_environement_overridden(self):
|
||||||
name = 'service'
|
name = 'service'
|
||||||
self.base_dir = 'tests/fixtures/environment-composefile'
|
self.base_dir = 'tests/fixtures/environment-composefile'
|
||||||
self.dispatch([
|
self.dispatch([
|
||||||
|
|
@ -1246,9 +1226,9 @@ class CLITestCase(DockerClientTestCase):
|
||||||
])
|
])
|
||||||
service = self.project.get_service(name)
|
service = self.project.get_service(name)
|
||||||
container = service.containers(stopped=True, one_off=OneOffFilter.only)[0]
|
container = service.containers(stopped=True, one_off=OneOffFilter.only)[0]
|
||||||
# env overridden
|
# env overriden
|
||||||
self.assertEqual('notbar', container.environment['foo'])
|
self.assertEqual('notbar', container.environment['foo'])
|
||||||
# keep environment from yaml
|
# keep environement from yaml
|
||||||
self.assertEqual('world', container.environment['hello'])
|
self.assertEqual('world', container.environment['hello'])
|
||||||
# added option from command line
|
# added option from command line
|
||||||
self.assertEqual('beta', container.environment['alpha'])
|
self.assertEqual('beta', container.environment['alpha'])
|
||||||
|
|
@ -1293,7 +1273,7 @@ class CLITestCase(DockerClientTestCase):
|
||||||
self.assertEqual(port_range[0], "0.0.0.0:49153")
|
self.assertEqual(port_range[0], "0.0.0.0:49153")
|
||||||
self.assertEqual(port_range[1], "0.0.0.0:49154")
|
self.assertEqual(port_range[1], "0.0.0.0:49154")
|
||||||
|
|
||||||
def test_run_service_with_explicitly_mapped_ports(self):
|
def test_run_service_with_explicitly_maped_ports(self):
|
||||||
# create one off container
|
# create one off container
|
||||||
self.base_dir = 'tests/fixtures/ports-composefile'
|
self.base_dir = 'tests/fixtures/ports-composefile'
|
||||||
self.dispatch(['run', '-d', '-p', '30000:3000', '--publish', '30001:3001', 'simple'])
|
self.dispatch(['run', '-d', '-p', '30000:3000', '--publish', '30001:3001', 'simple'])
|
||||||
|
|
@ -1310,7 +1290,7 @@ class CLITestCase(DockerClientTestCase):
|
||||||
self.assertEqual(port_short, "0.0.0.0:30000")
|
self.assertEqual(port_short, "0.0.0.0:30000")
|
||||||
self.assertEqual(port_full, "0.0.0.0:30001")
|
self.assertEqual(port_full, "0.0.0.0:30001")
|
||||||
|
|
||||||
def test_run_service_with_explicitly_mapped_ip_ports(self):
|
def test_run_service_with_explicitly_maped_ip_ports(self):
|
||||||
# create one off container
|
# create one off container
|
||||||
self.base_dir = 'tests/fixtures/ports-composefile'
|
self.base_dir = 'tests/fixtures/ports-composefile'
|
||||||
self.dispatch([
|
self.dispatch([
|
||||||
|
|
@ -1907,23 +1887,3 @@ class CLITestCase(DockerClientTestCase):
|
||||||
"BAZ=2",
|
"BAZ=2",
|
||||||
])
|
])
|
||||||
self.assertTrue(expected_env <= set(web.get('Config.Env')))
|
self.assertTrue(expected_env <= set(web.get('Config.Env')))
|
||||||
|
|
||||||
def test_top_services_not_running(self):
|
|
||||||
self.base_dir = 'tests/fixtures/top'
|
|
||||||
result = self.dispatch(['top'])
|
|
||||||
assert len(result.stdout) == 0
|
|
||||||
|
|
||||||
def test_top_services_running(self):
|
|
||||||
self.base_dir = 'tests/fixtures/top'
|
|
||||||
self.dispatch(['up', '-d'])
|
|
||||||
result = self.dispatch(['top'])
|
|
||||||
|
|
||||||
self.assertIn('top_service_a', result.stdout)
|
|
||||||
self.assertIn('top_service_b', result.stdout)
|
|
||||||
self.assertNotIn('top_not_a_service', result.stdout)
|
|
||||||
|
|
||||||
def test_top_processes_running(self):
|
|
||||||
self.base_dir = 'tests/fixtures/top'
|
|
||||||
self.dispatch(['up', '-d'])
|
|
||||||
result = self.dispatch(['top'])
|
|
||||||
assert result.stdout.count("top") == 4
|
|
||||||
|
|
|
||||||
9
tests/fixtures/extends/healthcheck-1.yml
vendored
9
tests/fixtures/extends/healthcheck-1.yml
vendored
|
|
@ -1,9 +0,0 @@
|
||||||
version: '2.1'
|
|
||||||
services:
|
|
||||||
demo:
|
|
||||||
image: foobar:latest
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "/health.sh"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 36
|
|
||||||
6
tests/fixtures/extends/healthcheck-2.yml
vendored
6
tests/fixtures/extends/healthcheck-2.yml
vendored
|
|
@ -1,6 +0,0 @@
|
||||||
version: '2.1'
|
|
||||||
services:
|
|
||||||
demo:
|
|
||||||
extends:
|
|
||||||
file: healthcheck-1.yml
|
|
||||||
service: demo
|
|
||||||
1
tests/fixtures/secrets/default
vendored
1
tests/fixtures/secrets/default
vendored
|
|
@ -1 +0,0 @@
|
||||||
This is the secret
|
|
||||||
6
tests/fixtures/top/docker-compose.yml
vendored
6
tests/fixtures/top/docker-compose.yml
vendored
|
|
@ -1,6 +0,0 @@
|
||||||
service_a:
|
|
||||||
image: busybox:latest
|
|
||||||
command: top
|
|
||||||
service_b:
|
|
||||||
image: busybox:latest
|
|
||||||
command: top
|
|
||||||
4
tests/fixtures/v3-full/docker-compose.yml
vendored
4
tests/fixtures/v3-full/docker-compose.yml
vendored
|
|
@ -35,7 +35,3 @@ services:
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
stop_grace_period: 20s
|
stop_grace_period: 20s
|
||||||
volumes:
|
|
||||||
foobar:
|
|
||||||
labels:
|
|
||||||
com.docker.compose.test: 'true'
|
|
||||||
|
|
|
||||||
2
tests/fixtures/volumes/docker-compose.yml
vendored
2
tests/fixtures/volumes/docker-compose.yml
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
version: '2.1'
|
|
||||||
services: {}
|
|
||||||
16
tests/fixtures/volumes/external-volumes.yml
vendored
16
tests/fixtures/volumes/external-volumes.yml
vendored
|
|
@ -1,16 +0,0 @@
|
||||||
version: "2.1"
|
|
||||||
|
|
||||||
services:
|
|
||||||
web:
|
|
||||||
image: busybox
|
|
||||||
command: top
|
|
||||||
volumes:
|
|
||||||
- foo:/var/lib/
|
|
||||||
- bar:/etc/
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
foo:
|
|
||||||
external: true
|
|
||||||
bar:
|
|
||||||
external:
|
|
||||||
name: some_bar
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os.path
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
@ -9,14 +8,12 @@ import pytest
|
||||||
from docker.errors import NotFound
|
from docker.errors import NotFound
|
||||||
|
|
||||||
from .. import mock
|
from .. import mock
|
||||||
from ..helpers import build_config as load_config
|
from ..helpers import build_config
|
||||||
from .testcases import DockerClientTestCase
|
from .testcases import DockerClientTestCase
|
||||||
from compose.config import config
|
from compose.config import config
|
||||||
from compose.config import ConfigurationError
|
from compose.config import ConfigurationError
|
||||||
from compose.config import types
|
|
||||||
from compose.config.config import V2_0
|
from compose.config.config import V2_0
|
||||||
from compose.config.config import V2_1
|
from compose.config.config import V2_1
|
||||||
from compose.config.config import V3_1
|
|
||||||
from compose.config.types import VolumeFromSpec
|
from compose.config.types import VolumeFromSpec
|
||||||
from compose.config.types import VolumeSpec
|
from compose.config.types import VolumeSpec
|
||||||
from compose.const import LABEL_PROJECT
|
from compose.const import LABEL_PROJECT
|
||||||
|
|
@ -29,16 +26,6 @@ from compose.project import ProjectError
|
||||||
from compose.service import ConvergenceStrategy
|
from compose.service import ConvergenceStrategy
|
||||||
from tests.integration.testcases import v2_1_only
|
from tests.integration.testcases import v2_1_only
|
||||||
from tests.integration.testcases import v2_only
|
from tests.integration.testcases import v2_only
|
||||||
from tests.integration.testcases import v3_only
|
|
||||||
|
|
||||||
|
|
||||||
def build_config(**kwargs):
|
|
||||||
return config.Config(
|
|
||||||
version=kwargs.get('version'),
|
|
||||||
services=kwargs.get('services'),
|
|
||||||
volumes=kwargs.get('volumes'),
|
|
||||||
networks=kwargs.get('networks'),
|
|
||||||
secrets=kwargs.get('secrets'))
|
|
||||||
|
|
||||||
|
|
||||||
class ProjectTest(DockerClientTestCase):
|
class ProjectTest(DockerClientTestCase):
|
||||||
|
|
@ -83,7 +70,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
def test_volumes_from_service(self):
|
def test_volumes_from_service(self):
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
config_data=load_config({
|
config_data=build_config({
|
||||||
'data': {
|
'data': {
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'volumes': ['/var/data'],
|
'volumes': ['/var/data'],
|
||||||
|
|
@ -109,7 +96,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
)
|
)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
config_data=load_config({
|
config_data=build_config({
|
||||||
'db': {
|
'db': {
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'volumes_from': ['composetest_data_container'],
|
'volumes_from': ['composetest_data_container'],
|
||||||
|
|
@ -125,7 +112,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
client=self.client,
|
client=self.client,
|
||||||
config_data=load_config({
|
config_data=build_config({
|
||||||
'version': V2_0,
|
'version': V2_0,
|
||||||
'services': {
|
'services': {
|
||||||
'net': {
|
'net': {
|
||||||
|
|
@ -152,7 +139,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
def get_project():
|
def get_project():
|
||||||
return Project.from_config(
|
return Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
config_data=load_config({
|
config_data=build_config({
|
||||||
'version': V2_0,
|
'version': V2_0,
|
||||||
'services': {
|
'services': {
|
||||||
'web': {
|
'web': {
|
||||||
|
|
@ -187,7 +174,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
def test_net_from_service_v1(self):
|
def test_net_from_service_v1(self):
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
config_data=load_config({
|
config_data=build_config({
|
||||||
'net': {
|
'net': {
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'command': ["top"]
|
'command': ["top"]
|
||||||
|
|
@ -211,7 +198,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
def get_project():
|
def get_project():
|
||||||
return Project.from_config(
|
return Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
config_data=load_config({
|
config_data=build_config({
|
||||||
'web': {
|
'web': {
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'net': 'container:composetest_net_container'
|
'net': 'container:composetest_net_container'
|
||||||
|
|
@ -482,7 +469,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
def test_project_up_starts_depends(self):
|
def test_project_up_starts_depends(self):
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
config_data=load_config({
|
config_data=build_config({
|
||||||
'console': {
|
'console': {
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'command': ["top"],
|
'command': ["top"],
|
||||||
|
|
@ -517,7 +504,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
def test_project_up_with_no_deps(self):
|
def test_project_up_with_no_deps(self):
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
config_data=load_config({
|
config_data=build_config({
|
||||||
'console': {
|
'console': {
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'command': ["top"],
|
'command': ["top"],
|
||||||
|
|
@ -577,7 +564,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
|
|
||||||
@v2_only()
|
@v2_only()
|
||||||
def test_project_up_networks(self):
|
def test_project_up_networks(self):
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -589,6 +576,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
'baz': {'aliases': ['extra']},
|
'baz': {'aliases': ['extra']},
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
|
volumes={},
|
||||||
networks={
|
networks={
|
||||||
'foo': {'driver': 'bridge'},
|
'foo': {'driver': 'bridge'},
|
||||||
'bar': {'driver': None},
|
'bar': {'driver': None},
|
||||||
|
|
@ -622,13 +610,14 @@ class ProjectTest(DockerClientTestCase):
|
||||||
|
|
||||||
@v2_only()
|
@v2_only()
|
||||||
def test_up_with_ipam_config(self):
|
def test_up_with_ipam_config(self):
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'networks': {'front': None},
|
'networks': {'front': None},
|
||||||
}],
|
}],
|
||||||
|
volumes={},
|
||||||
networks={
|
networks={
|
||||||
'front': {
|
'front': {
|
||||||
'driver': 'bridge',
|
'driver': 'bridge',
|
||||||
|
|
@ -682,7 +671,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
|
|
||||||
@v2_only()
|
@v2_only()
|
||||||
def test_up_with_network_static_addresses(self):
|
def test_up_with_network_static_addresses(self):
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -695,6 +684,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
|
volumes={},
|
||||||
networks={
|
networks={
|
||||||
'static_test': {
|
'static_test': {
|
||||||
'driver': 'bridge',
|
'driver': 'bridge',
|
||||||
|
|
@ -736,7 +726,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
@v2_1_only()
|
@v2_1_only()
|
||||||
def test_up_with_enable_ipv6(self):
|
def test_up_with_enable_ipv6(self):
|
||||||
self.require_api_version('1.23')
|
self.require_api_version('1.23')
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -748,6 +738,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
|
volumes={},
|
||||||
networks={
|
networks={
|
||||||
'static_test': {
|
'static_test': {
|
||||||
'driver': 'bridge',
|
'driver': 'bridge',
|
||||||
|
|
@ -779,7 +770,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
|
|
||||||
@v2_only()
|
@v2_only()
|
||||||
def test_up_with_network_static_addresses_missing_subnet(self):
|
def test_up_with_network_static_addresses_missing_subnet(self):
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -791,6 +782,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
|
volumes={},
|
||||||
networks={
|
networks={
|
||||||
'static_test': {
|
'static_test': {
|
||||||
'driver': 'bridge',
|
'driver': 'bridge',
|
||||||
|
|
@ -815,7 +807,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
|
|
||||||
@v2_1_only()
|
@v2_1_only()
|
||||||
def test_up_with_network_link_local_ips(self):
|
def test_up_with_network_link_local_ips(self):
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_1,
|
version=V2_1,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -826,6 +818,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
volumes={},
|
||||||
networks={
|
networks={
|
||||||
'linklocaltest': {'driver': 'bridge'}
|
'linklocaltest': {'driver': 'bridge'}
|
||||||
}
|
}
|
||||||
|
|
@ -851,13 +844,15 @@ class ProjectTest(DockerClientTestCase):
|
||||||
@v2_1_only()
|
@v2_1_only()
|
||||||
def test_up_with_isolation(self):
|
def test_up_with_isolation(self):
|
||||||
self.require_api_version('1.24')
|
self.require_api_version('1.24')
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_1,
|
version=V2_1,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'isolation': 'default'
|
'isolation': 'default'
|
||||||
}],
|
}],
|
||||||
|
volumes={},
|
||||||
|
networks={}
|
||||||
)
|
)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
client=self.client,
|
client=self.client,
|
||||||
|
|
@ -871,13 +866,15 @@ class ProjectTest(DockerClientTestCase):
|
||||||
@v2_1_only()
|
@v2_1_only()
|
||||||
def test_up_with_invalid_isolation(self):
|
def test_up_with_invalid_isolation(self):
|
||||||
self.require_api_version('1.24')
|
self.require_api_version('1.24')
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_1,
|
version=V2_1,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'isolation': 'foobar'
|
'isolation': 'foobar'
|
||||||
}],
|
}],
|
||||||
|
volumes={},
|
||||||
|
networks={}
|
||||||
)
|
)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
client=self.client,
|
client=self.client,
|
||||||
|
|
@ -890,13 +887,14 @@ class ProjectTest(DockerClientTestCase):
|
||||||
@v2_only()
|
@v2_only()
|
||||||
def test_project_up_with_network_internal(self):
|
def test_project_up_with_network_internal(self):
|
||||||
self.require_api_version('1.23')
|
self.require_api_version('1.23')
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'networks': {'internal': None},
|
'networks': {'internal': None},
|
||||||
}],
|
}],
|
||||||
|
volumes={},
|
||||||
networks={
|
networks={
|
||||||
'internal': {'driver': 'bridge', 'internal': True},
|
'internal': {'driver': 'bridge', 'internal': True},
|
||||||
},
|
},
|
||||||
|
|
@ -919,13 +917,14 @@ class ProjectTest(DockerClientTestCase):
|
||||||
|
|
||||||
network_name = 'network_with_label'
|
network_name = 'network_with_label'
|
||||||
|
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
'image': 'busybox:latest',
|
'image': 'busybox:latest',
|
||||||
'networks': {network_name: None}
|
'networks': {network_name: None}
|
||||||
}],
|
}],
|
||||||
|
volumes={},
|
||||||
networks={
|
networks={
|
||||||
network_name: {'labels': {'label_key': 'label_val'}}
|
network_name: {'labels': {'label_key': 'label_val'}}
|
||||||
}
|
}
|
||||||
|
|
@ -952,7 +951,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
def test_project_up_volumes(self):
|
def test_project_up_volumes(self):
|
||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -960,6 +959,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}],
|
}],
|
||||||
volumes={vol_name: {'driver': 'local'}},
|
volumes={vol_name: {'driver': 'local'}},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
|
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
|
|
@ -979,7 +979,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
|
|
||||||
volume_name = 'volume_with_label'
|
volume_name = 'volume_with_label'
|
||||||
|
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -993,6 +993,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
|
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
|
|
@ -1105,7 +1106,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
def test_initialize_volumes(self):
|
def test_initialize_volumes(self):
|
||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -1113,6 +1114,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}],
|
}],
|
||||||
volumes={vol_name: {}},
|
volumes={vol_name: {}},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
|
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
|
|
@ -1122,14 +1124,14 @@ class ProjectTest(DockerClientTestCase):
|
||||||
project.volumes.initialize()
|
project.volumes.initialize()
|
||||||
|
|
||||||
volume_data = self.client.inspect_volume(full_vol_name)
|
volume_data = self.client.inspect_volume(full_vol_name)
|
||||||
assert volume_data['Name'] == full_vol_name
|
self.assertEqual(volume_data['Name'], full_vol_name)
|
||||||
assert volume_data['Driver'] == 'local'
|
self.assertEqual(volume_data['Driver'], 'local')
|
||||||
|
|
||||||
@v2_only()
|
@v2_only()
|
||||||
def test_project_up_implicit_volume_driver(self):
|
def test_project_up_implicit_volume_driver(self):
|
||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -1137,6 +1139,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}],
|
}],
|
||||||
volumes={vol_name: {}},
|
volumes={vol_name: {}},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
|
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
|
|
@ -1149,47 +1152,11 @@ class ProjectTest(DockerClientTestCase):
|
||||||
self.assertEqual(volume_data['Name'], full_vol_name)
|
self.assertEqual(volume_data['Name'], full_vol_name)
|
||||||
self.assertEqual(volume_data['Driver'], 'local')
|
self.assertEqual(volume_data['Driver'], 'local')
|
||||||
|
|
||||||
@v3_only()
|
|
||||||
def test_project_up_with_secrets(self):
|
|
||||||
create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default'))
|
|
||||||
|
|
||||||
config_data = build_config(
|
|
||||||
version=V3_1,
|
|
||||||
services=[{
|
|
||||||
'name': 'web',
|
|
||||||
'image': 'busybox:latest',
|
|
||||||
'command': 'cat /run/secrets/special',
|
|
||||||
'secrets': [
|
|
||||||
types.ServiceSecret.parse({'source': 'super', 'target': 'special'}),
|
|
||||||
],
|
|
||||||
}],
|
|
||||||
secrets={
|
|
||||||
'super': {
|
|
||||||
'file': os.path.abspath('tests/fixtures/secrets/default'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
project = Project.from_config(
|
|
||||||
client=self.client,
|
|
||||||
name='composetest',
|
|
||||||
config_data=config_data,
|
|
||||||
)
|
|
||||||
project.up()
|
|
||||||
project.stop()
|
|
||||||
|
|
||||||
containers = project.containers(stopped=True)
|
|
||||||
assert len(containers) == 1
|
|
||||||
container, = containers
|
|
||||||
|
|
||||||
output = container.logs()
|
|
||||||
assert output == b"This is the secret\n"
|
|
||||||
|
|
||||||
@v2_only()
|
@v2_only()
|
||||||
def test_initialize_volumes_invalid_volume_driver(self):
|
def test_initialize_volumes_invalid_volume_driver(self):
|
||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
|
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -1197,6 +1164,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}],
|
}],
|
||||||
volumes={vol_name: {'driver': 'foobar'}},
|
volumes={vol_name: {'driver': 'foobar'}},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
|
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
|
|
@ -1211,7 +1179,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||||
|
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -1219,6 +1187,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}],
|
}],
|
||||||
volumes={vol_name: {'driver': 'local'}},
|
volumes={vol_name: {'driver': 'local'}},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
|
|
@ -1249,7 +1218,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||||
|
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -1257,6 +1226,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
'command': 'top'
|
'command': 'top'
|
||||||
}],
|
}],
|
||||||
volumes={vol_name: {'driver': 'local'}},
|
volumes={vol_name: {'driver': 'local'}},
|
||||||
|
networks={},
|
||||||
)
|
)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
|
|
@ -1287,7 +1257,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
vol_name = 'composetest_{0:x}'.format(random.getrandbits(32))
|
vol_name = 'composetest_{0:x}'.format(random.getrandbits(32))
|
||||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||||
self.client.create_volume(vol_name)
|
self.client.create_volume(vol_name)
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -1297,6 +1267,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
volumes={
|
volumes={
|
||||||
vol_name: {'external': True, 'external_name': vol_name}
|
vol_name: {'external': True, 'external_name': vol_name}
|
||||||
},
|
},
|
||||||
|
networks=None,
|
||||||
)
|
)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
|
|
@ -1311,7 +1282,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
def test_initialize_volumes_inexistent_external_volume(self):
|
def test_initialize_volumes_inexistent_external_volume(self):
|
||||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||||
|
|
||||||
config_data = build_config(
|
config_data = config.Config(
|
||||||
version=V2_0,
|
version=V2_0,
|
||||||
services=[{
|
services=[{
|
||||||
'name': 'web',
|
'name': 'web',
|
||||||
|
|
@ -1321,6 +1292,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
volumes={
|
volumes={
|
||||||
vol_name: {'external': True, 'external_name': vol_name}
|
vol_name: {'external': True, 'external_name': vol_name}
|
||||||
},
|
},
|
||||||
|
networks=None,
|
||||||
)
|
)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
|
|
@ -1377,7 +1349,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config_data = load_config(config_dict)
|
config_data = build_config(config_dict)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest', config_data=config_data, client=self.client
|
name='composetest', config_data=config_data, client=self.client
|
||||||
)
|
)
|
||||||
|
|
@ -1385,7 +1357,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
config_dict['service2'] = config_dict['service1']
|
config_dict['service2'] = config_dict['service1']
|
||||||
del config_dict['service1']
|
del config_dict['service1']
|
||||||
|
|
||||||
config_data = load_config(config_dict)
|
config_data = build_config(config_dict)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest', config_data=config_data, client=self.client
|
name='composetest', config_data=config_data, client=self.client
|
||||||
)
|
)
|
||||||
|
|
@ -1430,7 +1402,7 @@ class ProjectTest(DockerClientTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config_data = load_config(config_dict)
|
config_data = build_config(config_dict)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest', config_data=config_data, client=self.client
|
name='composetest', config_data=config_data, client=self.client
|
||||||
)
|
)
|
||||||
|
|
@ -1467,11 +1439,11 @@ class ProjectTest(DockerClientTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config_data = load_config(config_dict)
|
config_data = build_config(config_dict)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest', config_data=config_data, client=self.client
|
name='composetest', config_data=config_data, client=self.client
|
||||||
)
|
)
|
||||||
with pytest.raises(ProjectError):
|
with pytest.raises(HealthCheckFailed):
|
||||||
project.up()
|
project.up()
|
||||||
containers = project.containers()
|
containers = project.containers()
|
||||||
assert len(containers) == 1
|
assert len(containers) == 1
|
||||||
|
|
@ -1503,11 +1475,11 @@ class ProjectTest(DockerClientTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config_data = load_config(config_dict)
|
config_data = build_config(config_dict)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest', config_data=config_data, client=self.client
|
name='composetest', config_data=config_data, client=self.client
|
||||||
)
|
)
|
||||||
with pytest.raises(ProjectError):
|
with pytest.raises(NoHealthCheckConfigured):
|
||||||
project.up()
|
project.up()
|
||||||
containers = project.containers()
|
containers = project.containers()
|
||||||
assert len(containers) == 1
|
assert len(containers) == 1
|
||||||
|
|
@ -1517,30 +1489,3 @@ class ProjectTest(DockerClientTestCase):
|
||||||
assert 'svc1' in svc2.get_dependency_names()
|
assert 'svc1' in svc2.get_dependency_names()
|
||||||
with pytest.raises(NoHealthCheckConfigured):
|
with pytest.raises(NoHealthCheckConfigured):
|
||||||
svc1.is_healthy()
|
svc1.is_healthy()
|
||||||
|
|
||||||
|
|
||||||
def create_host_file(client, filename):
|
|
||||||
dirname = os.path.dirname(filename)
|
|
||||||
|
|
||||||
with open(filename, 'r') as fh:
|
|
||||||
content = fh.read()
|
|
||||||
|
|
||||||
container = client.create_container(
|
|
||||||
'busybox:latest',
|
|
||||||
['sh', '-c', 'echo -n "{}" > {}'.format(content, filename)],
|
|
||||||
volumes={dirname: {}},
|
|
||||||
host_config=client.create_host_config(
|
|
||||||
binds={dirname: {'bind': dirname, 'ro': False}},
|
|
||||||
network_mode='none',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
try:
|
|
||||||
client.start(container)
|
|
||||||
exitcode = client.wait(container)
|
|
||||||
|
|
||||||
if exitcode != 0:
|
|
||||||
output = client.logs(container)
|
|
||||||
raise Exception(
|
|
||||||
"Container exited with code {}:\n{}".format(exitcode, output))
|
|
||||||
finally:
|
|
||||||
client.remove_container(container, force=True)
|
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ from compose.config.config import resolve_environment
|
||||||
from compose.config.config import V1
|
from compose.config.config import V1
|
||||||
from compose.config.config import V2_0
|
from compose.config.config import V2_0
|
||||||
from compose.config.config import V2_1
|
from compose.config.config import V2_1
|
||||||
from compose.config.config import V3_0
|
|
||||||
from compose.config.environment import Environment
|
from compose.config.environment import Environment
|
||||||
from compose.const import API_VERSIONS
|
from compose.const import API_VERSIONS
|
||||||
from compose.const import LABEL_PROJECT
|
from compose.const import LABEL_PROJECT
|
||||||
|
|
@ -37,24 +36,21 @@ def get_links(container):
|
||||||
|
|
||||||
def engine_max_version():
|
def engine_max_version():
|
||||||
if 'DOCKER_VERSION' not in os.environ:
|
if 'DOCKER_VERSION' not in os.environ:
|
||||||
return V3_0
|
return V2_1
|
||||||
version = os.environ['DOCKER_VERSION'].partition('-')[0]
|
version = os.environ['DOCKER_VERSION'].partition('-')[0]
|
||||||
if version_lt(version, '1.10'):
|
if version_lt(version, '1.10'):
|
||||||
return V1
|
return V1
|
||||||
if version_lt(version, '1.12'):
|
elif version_lt(version, '1.12'):
|
||||||
return V2_0
|
return V2_0
|
||||||
if version_lt(version, '1.13'):
|
|
||||||
return V2_1
|
return V2_1
|
||||||
return V3_0
|
|
||||||
|
|
||||||
|
|
||||||
def build_version_required_decorator(ignored_versions):
|
def build_version_required_decorator(ignored_versions):
|
||||||
def decorator(f):
|
def decorator(f):
|
||||||
@functools.wraps(f)
|
@functools.wraps(f)
|
||||||
def wrapper(self, *args, **kwargs):
|
def wrapper(self, *args, **kwargs):
|
||||||
max_version = engine_max_version()
|
if engine_max_version() in ignored_versions:
|
||||||
if max_version in ignored_versions:
|
skip("Engine version is too low")
|
||||||
skip("Engine version %s is too low" % max_version)
|
|
||||||
return
|
return
|
||||||
return f(self, *args, **kwargs)
|
return f(self, *args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
|
||||||
|
|
@ -77,8 +77,7 @@ def test_to_bundle():
|
||||||
version=2,
|
version=2,
|
||||||
services=services,
|
services=services,
|
||||||
volumes={'special': {}},
|
volumes={'special': {}},
|
||||||
networks={'extra': {}},
|
networks={'extra': {}})
|
||||||
secrets={})
|
|
||||||
|
|
||||||
with mock.patch('compose.bundle.log.warn', autospec=True) as mock_log:
|
with mock.patch('compose.bundle.log.warn', autospec=True) as mock_log:
|
||||||
output = bundle.to_bundle(config, image_digests)
|
output = bundle.to_bundle(config, image_digests)
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ class CLITestCase(unittest.TestCase):
|
||||||
mock_client.create_host_config.call_args[1].get('restart_policy')
|
mock_client.create_host_config.call_args[1].get('restart_policy')
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_command_manual_and_service_ports_together(self):
|
def test_command_manula_and_service_ports_together(self):
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
client=None,
|
client=None,
|
||||||
|
|
|
||||||
|
|
@ -13,19 +13,15 @@ import pytest
|
||||||
|
|
||||||
from ...helpers import build_config_details
|
from ...helpers import build_config_details
|
||||||
from compose.config import config
|
from compose.config import config
|
||||||
from compose.config import types
|
|
||||||
from compose.config.config import resolve_build_args
|
from compose.config.config import resolve_build_args
|
||||||
from compose.config.config import resolve_environment
|
from compose.config.config import resolve_environment
|
||||||
from compose.config.config import V1
|
from compose.config.config import V1
|
||||||
from compose.config.config import V2_0
|
from compose.config.config import V2_0
|
||||||
from compose.config.config import V2_1
|
from compose.config.config import V2_1
|
||||||
from compose.config.config import V3_0
|
from compose.config.config import V3_0
|
||||||
from compose.config.config import V3_1
|
|
||||||
from compose.config.environment import Environment
|
from compose.config.environment import Environment
|
||||||
from compose.config.errors import ConfigurationError
|
from compose.config.errors import ConfigurationError
|
||||||
from compose.config.errors import VERSION_EXPLANATION
|
from compose.config.errors import VERSION_EXPLANATION
|
||||||
from compose.config.serialize import denormalize_service_dict
|
|
||||||
from compose.config.serialize import serialize_ns_time_value
|
|
||||||
from compose.config.types import VolumeSpec
|
from compose.config.types import VolumeSpec
|
||||||
from compose.const import IS_WINDOWS_PLATFORM
|
from compose.const import IS_WINDOWS_PLATFORM
|
||||||
from compose.utils import nanoseconds_from_time_seconds
|
from compose.utils import nanoseconds_from_time_seconds
|
||||||
|
|
@ -54,10 +50,6 @@ def service_sort(services):
|
||||||
return sorted(services, key=itemgetter('name'))
|
return sorted(services, key=itemgetter('name'))
|
||||||
|
|
||||||
|
|
||||||
def secret_sort(secrets):
|
|
||||||
return sorted(secrets, key=itemgetter('source'))
|
|
||||||
|
|
||||||
|
|
||||||
class ConfigTest(unittest.TestCase):
|
class ConfigTest(unittest.TestCase):
|
||||||
def test_load(self):
|
def test_load(self):
|
||||||
service_dicts = config.load(
|
service_dicts = config.load(
|
||||||
|
|
@ -174,9 +166,6 @@ class ConfigTest(unittest.TestCase):
|
||||||
cfg = config.load(build_config_details({'version': version}))
|
cfg = config.load(build_config_details({'version': version}))
|
||||||
assert cfg.version == V3_0
|
assert cfg.version == V3_0
|
||||||
|
|
||||||
cfg = config.load(build_config_details({'version': '3.1'}))
|
|
||||||
assert cfg.version == V3_1
|
|
||||||
|
|
||||||
def test_v1_file_version(self):
|
def test_v1_file_version(self):
|
||||||
cfg = config.load(build_config_details({'web': {'image': 'busybox'}}))
|
cfg = config.load(build_config_details({'web': {'image': 'busybox'}}))
|
||||||
assert cfg.version == V1
|
assert cfg.version == V1
|
||||||
|
|
@ -1723,90 +1712,6 @@ class ConfigTest(unittest.TestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_merge_depends_on_no_override(self):
|
|
||||||
base = {
|
|
||||||
'image': 'busybox',
|
|
||||||
'depends_on': {
|
|
||||||
'app1': {'condition': 'service_started'},
|
|
||||||
'app2': {'condition': 'service_healthy'}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override = {}
|
|
||||||
actual = config.merge_service_dicts(base, override, V2_1)
|
|
||||||
assert actual == base
|
|
||||||
|
|
||||||
def test_merge_depends_on_mixed_syntax(self):
|
|
||||||
base = {
|
|
||||||
'image': 'busybox',
|
|
||||||
'depends_on': {
|
|
||||||
'app1': {'condition': 'service_started'},
|
|
||||||
'app2': {'condition': 'service_healthy'}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override = {
|
|
||||||
'depends_on': ['app3']
|
|
||||||
}
|
|
||||||
|
|
||||||
actual = config.merge_service_dicts(base, override, V2_1)
|
|
||||||
assert actual == {
|
|
||||||
'image': 'busybox',
|
|
||||||
'depends_on': {
|
|
||||||
'app1': {'condition': 'service_started'},
|
|
||||||
'app2': {'condition': 'service_healthy'},
|
|
||||||
'app3': {'condition': 'service_started'}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_merge_pid(self):
|
|
||||||
# Regression: https://github.com/docker/compose/issues/4184
|
|
||||||
base = {
|
|
||||||
'image': 'busybox',
|
|
||||||
'pid': 'host'
|
|
||||||
}
|
|
||||||
|
|
||||||
override = {
|
|
||||||
'labels': {'com.docker.compose.test': 'yes'}
|
|
||||||
}
|
|
||||||
|
|
||||||
actual = config.merge_service_dicts(base, override, V2_0)
|
|
||||||
assert actual == {
|
|
||||||
'image': 'busybox',
|
|
||||||
'pid': 'host',
|
|
||||||
'labels': {'com.docker.compose.test': 'yes'}
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_merge_different_secrets(self):
|
|
||||||
base = {
|
|
||||||
'image': 'busybox',
|
|
||||||
'secrets': [
|
|
||||||
{'source': 'src.txt'}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
override = {'secrets': ['other-src.txt']}
|
|
||||||
|
|
||||||
actual = config.merge_service_dicts(base, override, V3_1)
|
|
||||||
assert secret_sort(actual['secrets']) == secret_sort([
|
|
||||||
{'source': 'src.txt'},
|
|
||||||
{'source': 'other-src.txt'}
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_merge_secrets_override(self):
|
|
||||||
base = {
|
|
||||||
'image': 'busybox',
|
|
||||||
'secrets': ['src.txt'],
|
|
||||||
}
|
|
||||||
override = {
|
|
||||||
'secrets': [
|
|
||||||
{
|
|
||||||
'source': 'src.txt',
|
|
||||||
'target': 'data.txt',
|
|
||||||
'mode': 0o400
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
actual = config.merge_service_dicts(base, override, V3_1)
|
|
||||||
assert actual['secrets'] == override['secrets']
|
|
||||||
|
|
||||||
def test_external_volume_config(self):
|
def test_external_volume_config(self):
|
||||||
config_details = build_config_details({
|
config_details = build_config_details({
|
||||||
'version': '2',
|
'version': '2',
|
||||||
|
|
@ -1886,91 +1791,6 @@ class ConfigTest(unittest.TestCase):
|
||||||
config.load(config_details)
|
config.load(config_details)
|
||||||
assert 'has neither an image nor a build context' in exc.exconly()
|
assert 'has neither an image nor a build context' in exc.exconly()
|
||||||
|
|
||||||
def test_load_secrets(self):
|
|
||||||
base_file = config.ConfigFile(
|
|
||||||
'base.yaml',
|
|
||||||
{
|
|
||||||
'version': '3.1',
|
|
||||||
'services': {
|
|
||||||
'web': {
|
|
||||||
'image': 'example/web',
|
|
||||||
'secrets': [
|
|
||||||
'one',
|
|
||||||
{
|
|
||||||
'source': 'source',
|
|
||||||
'target': 'target',
|
|
||||||
'uid': '100',
|
|
||||||
'gid': '200',
|
|
||||||
'mode': 0o777,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'secrets': {
|
|
||||||
'one': {'file': 'secret.txt'},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
details = config.ConfigDetails('.', [base_file])
|
|
||||||
service_dicts = config.load(details).services
|
|
||||||
expected = [
|
|
||||||
{
|
|
||||||
'name': 'web',
|
|
||||||
'image': 'example/web',
|
|
||||||
'secrets': [
|
|
||||||
types.ServiceSecret('one', None, None, None, None),
|
|
||||||
types.ServiceSecret('source', 'target', '100', '200', 0o777),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
assert service_sort(service_dicts) == service_sort(expected)
|
|
||||||
|
|
||||||
def test_load_secrets_multi_file(self):
|
|
||||||
base_file = config.ConfigFile(
|
|
||||||
'base.yaml',
|
|
||||||
{
|
|
||||||
'version': '3.1',
|
|
||||||
'services': {
|
|
||||||
'web': {
|
|
||||||
'image': 'example/web',
|
|
||||||
'secrets': ['one'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'secrets': {
|
|
||||||
'one': {'file': 'secret.txt'},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
override_file = config.ConfigFile(
|
|
||||||
'base.yaml',
|
|
||||||
{
|
|
||||||
'version': '3.1',
|
|
||||||
'services': {
|
|
||||||
'web': {
|
|
||||||
'secrets': [
|
|
||||||
{
|
|
||||||
'source': 'source',
|
|
||||||
'target': 'target',
|
|
||||||
'uid': '100',
|
|
||||||
'gid': '200',
|
|
||||||
'mode': 0o777,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
details = config.ConfigDetails('.', [base_file, override_file])
|
|
||||||
service_dicts = config.load(details).services
|
|
||||||
expected = [
|
|
||||||
{
|
|
||||||
'name': 'web',
|
|
||||||
'image': 'example/web',
|
|
||||||
'secrets': [
|
|
||||||
types.ServiceSecret('one', None, None, None, None),
|
|
||||||
types.ServiceSecret('source', 'target', '100', '200', 0o777),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
assert service_sort(service_dicts) == service_sort(expected)
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkModeTest(unittest.TestCase):
|
class NetworkModeTest(unittest.TestCase):
|
||||||
def test_network_mode_standard(self):
|
def test_network_mode_standard(self):
|
||||||
|
|
@ -3242,19 +3062,6 @@ class ExtendsTest(unittest.TestCase):
|
||||||
'other': {'condition': 'service_started'}
|
'other': {'condition': 'service_started'}
|
||||||
}
|
}
|
||||||
|
|
||||||
def test_extends_with_healthcheck(self):
|
|
||||||
service_dicts = load_from_filename('tests/fixtures/extends/healthcheck-2.yml')
|
|
||||||
assert service_sort(service_dicts) == [{
|
|
||||||
'name': 'demo',
|
|
||||||
'image': 'foobar:latest',
|
|
||||||
'healthcheck': {
|
|
||||||
'test': ['CMD', '/health.sh'],
|
|
||||||
'interval': 10000000000,
|
|
||||||
'timeout': 5000000000,
|
|
||||||
'retries': 36,
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(IS_WINDOWS_PLATFORM, reason='paths use slash')
|
@pytest.mark.xfail(IS_WINDOWS_PLATFORM, reason='paths use slash')
|
||||||
class ExpandPathTest(unittest.TestCase):
|
class ExpandPathTest(unittest.TestCase):
|
||||||
|
|
@ -3462,89 +3269,3 @@ def get_config_filename_for_files(filenames, subdir=None):
|
||||||
return os.path.basename(filename)
|
return os.path.basename(filename)
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(project_dir)
|
shutil.rmtree(project_dir)
|
||||||
|
|
||||||
|
|
||||||
class SerializeTest(unittest.TestCase):
|
|
||||||
def test_denormalize_depends_on_v3(self):
|
|
||||||
service_dict = {
|
|
||||||
'image': 'busybox',
|
|
||||||
'command': 'true',
|
|
||||||
'depends_on': {
|
|
||||||
'service2': {'condition': 'service_started'},
|
|
||||||
'service3': {'condition': 'service_started'},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert denormalize_service_dict(service_dict, V3_0) == {
|
|
||||||
'image': 'busybox',
|
|
||||||
'command': 'true',
|
|
||||||
'depends_on': ['service2', 'service3']
|
|
||||||
}
|
|
||||||
|
|
||||||
def test_denormalize_depends_on_v2_1(self):
|
|
||||||
service_dict = {
|
|
||||||
'image': 'busybox',
|
|
||||||
'command': 'true',
|
|
||||||
'depends_on': {
|
|
||||||
'service2': {'condition': 'service_started'},
|
|
||||||
'service3': {'condition': 'service_started'},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert denormalize_service_dict(service_dict, V2_1) == service_dict
|
|
||||||
|
|
||||||
def test_serialize_time(self):
|
|
||||||
data = {
|
|
||||||
9: '9ns',
|
|
||||||
9000: '9us',
|
|
||||||
9000000: '9ms',
|
|
||||||
90000000: '90ms',
|
|
||||||
900000000: '900ms',
|
|
||||||
999999999: '999999999ns',
|
|
||||||
1000000000: '1s',
|
|
||||||
60000000000: '1m',
|
|
||||||
60000000001: '60000000001ns',
|
|
||||||
9000000000000: '150m',
|
|
||||||
90000000000000: '25h',
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v in data.items():
|
|
||||||
assert serialize_ns_time_value(k) == v
|
|
||||||
|
|
||||||
def test_denormalize_healthcheck(self):
|
|
||||||
service_dict = {
|
|
||||||
'image': 'test',
|
|
||||||
'healthcheck': {
|
|
||||||
'test': 'exit 1',
|
|
||||||
'interval': '1m40s',
|
|
||||||
'timeout': '30s',
|
|
||||||
'retries': 5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
processed_service = config.process_service(config.ServiceConfig(
|
|
||||||
'.', 'test', 'test', service_dict
|
|
||||||
))
|
|
||||||
denormalized_service = denormalize_service_dict(processed_service, V2_1)
|
|
||||||
assert denormalized_service['healthcheck']['interval'] == '100s'
|
|
||||||
assert denormalized_service['healthcheck']['timeout'] == '30s'
|
|
||||||
|
|
||||||
def test_denormalize_secrets(self):
|
|
||||||
service_dict = {
|
|
||||||
'name': 'web',
|
|
||||||
'image': 'example/web',
|
|
||||||
'secrets': [
|
|
||||||
types.ServiceSecret('one', None, None, None, None),
|
|
||||||
types.ServiceSecret('source', 'target', '100', '200', 0o777),
|
|
||||||
],
|
|
||||||
}
|
|
||||||
denormalized_service = denormalize_service_dict(service_dict, V3_1)
|
|
||||||
assert secret_sort(denormalized_service['secrets']) == secret_sort([
|
|
||||||
{'source': 'one'},
|
|
||||||
{
|
|
||||||
'source': 'source',
|
|
||||||
'target': 'target',
|
|
||||||
'uid': '100',
|
|
||||||
'gid': '200',
|
|
||||||
'mode': 0o777,
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
# encoding: utf-8
|
|
||||||
from __future__ import absolute_import
|
|
||||||
from __future__ import print_function
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from compose.config.environment import Environment
|
|
||||||
from tests import unittest
|
|
||||||
|
|
||||||
|
|
||||||
class EnvironmentTest(unittest.TestCase):
|
|
||||||
def test_get_simple(self):
|
|
||||||
env = Environment({
|
|
||||||
'FOO': 'bar',
|
|
||||||
'BAR': '1',
|
|
||||||
'BAZ': ''
|
|
||||||
})
|
|
||||||
|
|
||||||
assert env.get('FOO') == 'bar'
|
|
||||||
assert env.get('BAR') == '1'
|
|
||||||
assert env.get('BAZ') == ''
|
|
||||||
|
|
||||||
def test_get_undefined(self):
|
|
||||||
env = Environment({
|
|
||||||
'FOO': 'bar'
|
|
||||||
})
|
|
||||||
assert env.get('FOOBAR') is None
|
|
||||||
|
|
||||||
def test_get_boolean(self):
|
|
||||||
env = Environment({
|
|
||||||
'FOO': '',
|
|
||||||
'BAR': '0',
|
|
||||||
'BAZ': 'FALSE',
|
|
||||||
'FOOBAR': 'true',
|
|
||||||
})
|
|
||||||
|
|
||||||
assert env.get_boolean('FOO') is False
|
|
||||||
assert env.get_boolean('BAR') is False
|
|
||||||
assert env.get_boolean('BAZ') is False
|
|
||||||
assert env.get_boolean('FOOBAR') is True
|
|
||||||
assert env.get_boolean('UNDEFINED') is False
|
|
||||||
|
|
@ -36,7 +36,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
],
|
],
|
||||||
networks=None,
|
networks=None,
|
||||||
volumes=None,
|
volumes=None,
|
||||||
secrets=None,
|
|
||||||
)
|
)
|
||||||
project = Project.from_config(
|
project = Project.from_config(
|
||||||
name='composetest',
|
name='composetest',
|
||||||
|
|
@ -65,7 +64,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
],
|
],
|
||||||
networks=None,
|
networks=None,
|
||||||
volumes=None,
|
volumes=None,
|
||||||
secrets=None,
|
|
||||||
)
|
)
|
||||||
project = Project.from_config('composetest', config, None)
|
project = Project.from_config('composetest', config, None)
|
||||||
self.assertEqual(len(project.services), 2)
|
self.assertEqual(len(project.services), 2)
|
||||||
|
|
@ -172,7 +170,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
}],
|
}],
|
||||||
networks=None,
|
networks=None,
|
||||||
volumes=None,
|
volumes=None,
|
||||||
secrets=None,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
assert project.get_service('test')._get_volumes_from() == [container_id + ":rw"]
|
assert project.get_service('test')._get_volumes_from() == [container_id + ":rw"]
|
||||||
|
|
@ -205,7 +202,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
],
|
],
|
||||||
networks=None,
|
networks=None,
|
||||||
volumes=None,
|
volumes=None,
|
||||||
secrets=None,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
assert project.get_service('test')._get_volumes_from() == [container_name + ":rw"]
|
assert project.get_service('test')._get_volumes_from() == [container_name + ":rw"]
|
||||||
|
|
@ -231,7 +227,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
],
|
],
|
||||||
networks=None,
|
networks=None,
|
||||||
volumes=None,
|
volumes=None,
|
||||||
secrets=None,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
with mock.patch.object(Service, 'containers') as mock_return:
|
with mock.patch.object(Service, 'containers') as mock_return:
|
||||||
|
|
@ -365,7 +360,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
],
|
],
|
||||||
networks=None,
|
networks=None,
|
||||||
volumes=None,
|
volumes=None,
|
||||||
secrets=None,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
service = project.get_service('test')
|
service = project.get_service('test')
|
||||||
|
|
@ -390,7 +384,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
],
|
],
|
||||||
networks=None,
|
networks=None,
|
||||||
volumes=None,
|
volumes=None,
|
||||||
secrets=None,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
service = project.get_service('test')
|
service = project.get_service('test')
|
||||||
|
|
@ -424,7 +417,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
],
|
],
|
||||||
networks=None,
|
networks=None,
|
||||||
volumes=None,
|
volumes=None,
|
||||||
secrets=None,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -445,7 +437,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
],
|
],
|
||||||
networks=None,
|
networks=None,
|
||||||
volumes=None,
|
volumes=None,
|
||||||
secrets=None,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -466,7 +457,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
],
|
],
|
||||||
networks={'custom': {}},
|
networks={'custom': {}},
|
||||||
volumes=None,
|
volumes=None,
|
||||||
secrets=None,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -497,7 +487,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
}],
|
}],
|
||||||
networks=None,
|
networks=None,
|
||||||
volumes=None,
|
volumes=None,
|
||||||
secrets=None,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.assertEqual([c.id for c in project.containers()], ['1'])
|
self.assertEqual([c.id for c in project.containers()], ['1'])
|
||||||
|
|
@ -514,7 +503,6 @@ class ProjectTest(unittest.TestCase):
|
||||||
}],
|
}],
|
||||||
networks={'default': {}},
|
networks={'default': {}},
|
||||||
volumes={'data': {}},
|
volumes={'data': {}},
|
||||||
secrets=None,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
self.mock_client.remove_network.side_effect = NotFound(None, None, 'oops')
|
self.mock_client.remove_network.side_effect = NotFound(None, None, 'oops')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue