mirror of
https://github.com/docker/compose.git
synced 2026-02-10 10:39:23 +08:00
Compare commits
494 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d474515d45 | ||
|
|
2b21c5df93 | ||
|
|
1f3c10eb4b | ||
|
|
68ad165a59 | ||
|
|
3060ed2795 | ||
|
|
be09b2e8ce | ||
|
|
571a1af013 | ||
|
|
8f644eea73 | ||
|
|
56e92e34b6 | ||
|
|
a42a04dfe8 | ||
|
|
34bcd03a76 | ||
|
|
ed61e42f93 | ||
|
|
65696bb1cb | ||
|
|
446e00520c | ||
|
|
c01c9c29f4 | ||
|
|
038c81f34e | ||
|
|
a20b69ac5b | ||
|
|
977530c22a | ||
|
|
d4db8b6b12 | ||
|
|
f8ce0f04e6 | ||
|
|
8e0520e71e | ||
|
|
332311358e | ||
|
|
df9e420ddd | ||
|
|
142f5dba84 | ||
|
|
700c586bcf | ||
|
|
fc566509d5 | ||
|
|
e73c2303fb | ||
|
|
624303233a | ||
|
|
a1729c52de | ||
|
|
254224c182 | ||
|
|
0861e68450 | ||
|
|
af5b748500 | ||
|
|
32a22c1f4f | ||
|
|
e6ea8fb962 | ||
|
|
043465448f | ||
|
|
1d08390864 | ||
|
|
69a83d1303 | ||
|
|
781b9f1cd5 | ||
|
|
cbff0e5559 | ||
|
|
e4222bff53 | ||
|
|
25197fe6dd | ||
|
|
85cdaf9ddf | ||
|
|
a8469db83f | ||
|
|
08488dae59 | ||
|
|
cc3a216f2f | ||
|
|
6e818b9ae0 | ||
|
|
6b3e57503e | ||
|
|
8e497a1289 | ||
|
|
5aed704379 | ||
|
|
1ff9b758d2 | ||
|
|
9eaba55973 | ||
|
|
a85f8a40a9 | ||
|
|
095f65cb42 | ||
|
|
208e57ded8 | ||
|
|
2d148faedf | ||
|
|
43ac1e31c6 | ||
|
|
5561a778c9 | ||
|
|
ae48f488d1 | ||
|
|
5e3a095380 | ||
|
|
a2a3eb72e2 | ||
|
|
3513b42423 | ||
|
|
d4fa63fdcb | ||
|
|
c21d4cfb40 | ||
|
|
61f1d4f69b | ||
|
|
f7cce281de | ||
|
|
bcaacc7f23 | ||
|
|
3f5898f8d0 | ||
|
|
2bb67f2700 | ||
|
|
bf521fe3ab | ||
|
|
11e9621da5 | ||
|
|
a9de9abcfb | ||
|
|
799ab842a0 | ||
|
|
2f65ace2aa | ||
|
|
aa0a4189ee | ||
|
|
eba3ff8f37 | ||
|
|
6313365ba4 | ||
|
|
dbd51745c4 | ||
|
|
a8bfbc147a | ||
|
|
fbbd6f83d7 | ||
|
|
a000978980 | ||
|
|
361c0893a9 | ||
|
|
513b6128c2 | ||
|
|
eececb9add | ||
|
|
501b5acde6 | ||
|
|
f51bc4cd00 | ||
|
|
517f87a372 | ||
|
|
718049cbd7 | ||
|
|
02371f3127 | ||
|
|
a7c9de82b2 | ||
|
|
51ebeb5441 | ||
|
|
fafaa9c5b8 | ||
|
|
fc9c3cde06 | ||
|
|
73bfbab54b | ||
|
|
2ac081b4c4 | ||
|
|
eeea049f17 | ||
|
|
26064d4b60 | ||
|
|
7c46beb8af | ||
|
|
aa1ec4524c | ||
|
|
a4ee6ca7a5 | ||
|
|
5617eff0c2 | ||
|
|
fa24ab8e25 | ||
|
|
0aad9595a5 | ||
|
|
813900180e | ||
|
|
82417bd5bc | ||
|
|
0cbb73c024 | ||
|
|
38e3d670a9 | ||
|
|
24c78728e0 | ||
|
|
9eeb2d3154 | ||
|
|
8da82c98ef | ||
|
|
1a8c855489 | ||
|
|
15bd0b0c5f | ||
|
|
39d0f6477e | ||
|
|
3a95a0872d | ||
|
|
f794c79eb3 | ||
|
|
407d825705 | ||
|
|
82b41b9ebd | ||
|
|
6c06170eb0 | ||
|
|
60c1311f67 | ||
|
|
17add87e4e | ||
|
|
bf0418bac1 | ||
|
|
b9d0c77cde | ||
|
|
bdb8545611 | ||
|
|
41df35c1f4 | ||
|
|
3ef5045a08 | ||
|
|
d9df7aab6e | ||
|
|
c9d96b449b | ||
|
|
1744b45765 | ||
|
|
87f457e7dc | ||
|
|
abcc91e2b9 | ||
|
|
8b9fe89842 | ||
|
|
34b18194f7 | ||
|
|
ce27dba52e | ||
|
|
d2b9456137 | ||
|
|
9c60fe67df | ||
|
|
c16df17e1f | ||
|
|
20404db12b | ||
|
|
f2ff7fd75e | ||
|
|
cb00aaad28 | ||
|
|
e885bc084d | ||
|
|
73d3a25ebd | ||
|
|
3524bcfad0 | ||
|
|
1076f1d9a5 | ||
|
|
16652ed26a | ||
|
|
c6a76b9bd7 | ||
|
|
3a0e3ba7ee | ||
|
|
86ef8e62c3 | ||
|
|
8bf0627ea9 | ||
|
|
2e14191682 | ||
|
|
155f64182a | ||
|
|
8db0cba0af | ||
|
|
a7424435b3 | ||
|
|
d445ebba3f | ||
|
|
f592aad10d | ||
|
|
ef46445ed3 | ||
|
|
150593298e | ||
|
|
524a97e553 | ||
|
|
1d608e0338 | ||
|
|
329ad73922 | ||
|
|
b633c5c3e1 | ||
|
|
e6ef8629a8 | ||
|
|
d658fecc63 | ||
|
|
f9c7a0cc08 | ||
|
|
6e172d6b89 | ||
|
|
98e261ba32 | ||
|
|
11c7a25ae9 | ||
|
|
234036756b | ||
|
|
9c03797f9d | ||
|
|
485c0eba53 | ||
|
|
69384a9a0f | ||
|
|
1601ead7bc | ||
|
|
ea4ccf639d | ||
|
|
b1850ea4d4 | ||
|
|
adba639e88 | ||
|
|
d8518529c4 | ||
|
|
c79f15da9e | ||
|
|
3f55382ff0 | ||
|
|
44337d2bbf | ||
|
|
bc733508d6 | ||
|
|
c422b5447c | ||
|
|
e74441c907 | ||
|
|
2bac32a46e | ||
|
|
f278400fbc | ||
|
|
4f9db4d3e6 | ||
|
|
06bf339a42 | ||
|
|
231ea10058 | ||
|
|
46679150d6 | ||
|
|
d3d378b92a | ||
|
|
163cdfd31d | ||
|
|
25f85938bb | ||
|
|
cacbca859d | ||
|
|
ecac13f272 | ||
|
|
14793cc2e4 | ||
|
|
11d5ecdc75 | ||
|
|
9549a213ba | ||
|
|
da434013e3 | ||
|
|
ace69c96a7 | ||
|
|
2db04c1e40 | ||
|
|
8f7cd00481 | ||
|
|
5a6e1a7e2e | ||
|
|
06545d0668 | ||
|
|
075fd93452 | ||
|
|
d062ad739a | ||
|
|
7cd5209cc2 | ||
|
|
f79c28168b | ||
|
|
6a000dcff1 | ||
|
|
2636dcf064 | ||
|
|
77377f2df6 | ||
|
|
af1bc285a0 | ||
|
|
e1fd7a6567 | ||
|
|
de478f84b0 | ||
|
|
24a281fa5d | ||
|
|
54a5e7d4aa | ||
|
|
36ef5b3881 | ||
|
|
5694a2b0f8 | ||
|
|
695d3419cf | ||
|
|
981bcbbe7a | ||
|
|
5ec37d08a5 | ||
|
|
1a14fcb1e6 | ||
|
|
084a5ca312 | ||
|
|
d633c33a19 | ||
|
|
cc09f39f29 | ||
|
|
918b508bd5 | ||
|
|
10531f6302 | ||
|
|
bf1bd3fc60 | ||
|
|
02f40eea67 | ||
|
|
250c3112b9 | ||
|
|
495a087fb5 | ||
|
|
fd1f73a5e7 | ||
|
|
bf1dd0c267 | ||
|
|
1cf6dea444 | ||
|
|
1adc4cb16f | ||
|
|
2ed40e8042 | ||
|
|
7ad73c2899 | ||
|
|
2593256985 | ||
|
|
048fd136d2 | ||
|
|
663866cbe5 | ||
|
|
fb25e88a03 | ||
|
|
3635303372 | ||
|
|
2cee028e99 | ||
|
|
c78f84aeb5 | ||
|
|
5c6924ec6f | ||
|
|
9e8c8caa2b | ||
|
|
da8189cf22 | ||
|
|
eb5e018698 | ||
|
|
e64b96d0fa | ||
|
|
b81624185f | ||
|
|
591d9eb3a2 | ||
|
|
9c0b922597 | ||
|
|
c26d2fa7cf | ||
|
|
cfb110653f | ||
|
|
b53b8b24f1 | ||
|
|
36bf0c458b | ||
|
|
299fcd57fd | ||
|
|
b90cb48a1d | ||
|
|
f8808d81c5 | ||
|
|
2c9c60e007 | ||
|
|
9970a84ae6 | ||
|
|
5682480726 | ||
|
|
fd532a37e7 | ||
|
|
2aa568ceba | ||
|
|
4ae2e0ec55 | ||
|
|
922422a539 | ||
|
|
d239f0f318 | ||
|
|
ca734ce565 | ||
|
|
c9e070f0f6 | ||
|
|
b3792dd258 | ||
|
|
d8ee474e09 | ||
|
|
7d3616474d | ||
|
|
6a3501f901 | ||
|
|
5daed33c6a | ||
|
|
5c1e5f3fc7 | ||
|
|
b032999f06 | ||
|
|
5059a1d7bf | ||
|
|
54a525bbe6 | ||
|
|
8ceaf49296 | ||
|
|
bfee07e1eb | ||
|
|
1d32592056 | ||
|
|
85567ae092 | ||
|
|
b4280fb561 | ||
|
|
865a64afea | ||
|
|
29692b5921 | ||
|
|
2658c372a7 | ||
|
|
d71d8bce24 | ||
|
|
ff20b641c7 | ||
|
|
339b331a86 | ||
|
|
6fe69b2575 | ||
|
|
dd5614ec3b | ||
|
|
4b7b6adc76 | ||
|
|
466374bdd0 | ||
|
|
2e85b3c265 | ||
|
|
3371227794 | ||
|
|
e9dc82011f | ||
|
|
3950460703 | ||
|
|
3b541b071c | ||
|
|
25671ae622 | ||
|
|
0191e69d5b | ||
|
|
897d239fcb | ||
|
|
c5a760ce43 | ||
|
|
759d3bc9f9 | ||
|
|
26f687a12b | ||
|
|
ad41461309 | ||
|
|
db4ed89528 | ||
|
|
1b5fa3b93f | ||
|
|
86cd52370a | ||
|
|
4f97edf355 | ||
|
|
bc5fc6ba7e | ||
|
|
f937e42aaf | ||
|
|
f46ca459d2 | ||
|
|
b2d4c1b865 | ||
|
|
5a1ba0efcf | ||
|
|
17d4229e57 | ||
|
|
4efb89709c | ||
|
|
f35449a603 | ||
|
|
34b11c4f4f | ||
|
|
a7acf5fa67 | ||
|
|
c525373acb | ||
|
|
e99b8acea7 | ||
|
|
8ab8df86e0 | ||
|
|
de178267df | ||
|
|
1680f9a874 | ||
|
|
5c4f33702e | ||
|
|
e2762b144e | ||
|
|
58ec0e9fb6 | ||
|
|
a7224411b4 | ||
|
|
c5a88deeba | ||
|
|
349d9f34d0 | ||
|
|
9d38ee1726 | ||
|
|
697a48af32 | ||
|
|
9b0d1ffcf8 | ||
|
|
d10a179f3e | ||
|
|
94246f3cac | ||
|
|
9630cc5808 | ||
|
|
6c175548b8 | ||
|
|
f1431b2a48 | ||
|
|
9b0e3d53eb | ||
|
|
de3da829ea | ||
|
|
c79aabde32 | ||
|
|
eb5f7d76fe | ||
|
|
0aad322140 | ||
|
|
b1c06770d4 | ||
|
|
a220043ca0 | ||
|
|
e330f5905b | ||
|
|
aa56ab3e71 | ||
|
|
16c8099cf8 | ||
|
|
07bda5960e | ||
|
|
d203402998 | ||
|
|
894ab41c3b | ||
|
|
3ba66453d2 | ||
|
|
4ce63f53f7 | ||
|
|
2eca9313c2 | ||
|
|
1fea7c1beb | ||
|
|
acf2ffb0c7 | ||
|
|
0d48a93f57 | ||
|
|
aaa7ef6de5 | ||
|
|
6ef55a531f | ||
|
|
a553db33c6 | ||
|
|
338ffe07a0 | ||
|
|
05bec55d21 | ||
|
|
3c25af4fe9 | ||
|
|
8fdd45cd4c | ||
|
|
a0954dc59d | ||
|
|
0582001810 | ||
|
|
ac8ea082a1 | ||
|
|
da1a34a8f2 | ||
|
|
cdb6eb55d5 | ||
|
|
8b023ae07d | ||
|
|
e08140babe | ||
|
|
8c80297c76 | ||
|
|
05f1472f6a | ||
|
|
5728c43db8 | ||
|
|
7e991515bc | ||
|
|
d979115692 | ||
|
|
c653ddf439 | ||
|
|
a9cbb0f481 | ||
|
|
71bebc16ee | ||
|
|
b0dc374abc | ||
|
|
25d16d1fb4 | ||
|
|
ab97dcc7c5 | ||
|
|
a8bd3b7af1 | ||
|
|
bef8785fc2 | ||
|
|
811364b4f8 | ||
|
|
1551fcb4e6 | ||
|
|
143ac0f1b6 | ||
|
|
eb4249ece4 | ||
|
|
1e8241f67c | ||
|
|
388169011f | ||
|
|
053a560466 | ||
|
|
35e262e778 | ||
|
|
34ba0bc9dd | ||
|
|
56e38260ea | ||
|
|
83e020734d | ||
|
|
c28bf5227a | ||
|
|
326ee2ad69 | ||
|
|
51c113b623 | ||
|
|
194b8af242 | ||
|
|
0d895a23f8 | ||
|
|
7cdc7e15e3 | ||
|
|
fb6d92250c | ||
|
|
fb026543f2 | ||
|
|
d688d3bf22 | ||
|
|
898e1b605d | ||
|
|
f414bf7892 | ||
|
|
3c4593f2ad | ||
|
|
d2562029f6 | ||
|
|
26ed1051eb | ||
|
|
191c10b9d0 | ||
|
|
8b16ab1040 | ||
|
|
24d34040e3 | ||
|
|
dbe7819fd4 | ||
|
|
5d05df6e5c | ||
|
|
b621948c1b | ||
|
|
6f62bcccbb | ||
|
|
f5c53c2d07 | ||
|
|
1cfeda71eb | ||
|
|
e9c7a3a9d5 | ||
|
|
2bf2b22fbe | ||
|
|
8d81b87513 | ||
|
|
575f2ed7f2 | ||
|
|
5f4b22ed7c | ||
|
|
dcf6bd779f | ||
|
|
80823b77ef | ||
|
|
2c16e16db7 | ||
|
|
d1be9caf59 | ||
|
|
f659918743 | ||
|
|
c3c0cf3d94 | ||
|
|
eec822de8f | ||
|
|
6df3886b44 | ||
|
|
279874158c | ||
|
|
566d2207f3 | ||
|
|
d09c39dced | ||
|
|
a1d36379a3 | ||
|
|
0c55998b41 | ||
|
|
2f6ec9b777 | ||
|
|
06af729dc8 | ||
|
|
dcbf005fe4 | ||
|
|
e105f16527 | ||
|
|
ce5a0c656f | ||
|
|
f58f23a6a2 | ||
|
|
d82a1a5a03 | ||
|
|
9aa5232601 | ||
|
|
ccd83b8a74 | ||
|
|
9e57850c16 | ||
|
|
35d3a7ca56 | ||
|
|
6c998602bb | ||
|
|
402f368830 | ||
|
|
30dd3e66d7 | ||
|
|
0c4fa017b9 | ||
|
|
b12e23b0bd | ||
|
|
17da54da20 | ||
|
|
1baa4f4489 | ||
|
|
aefc2a111a | ||
|
|
7781b7c992 | ||
|
|
785835b1a2 | ||
|
|
e4fb5545f0 | ||
|
|
74cc091225 | ||
|
|
5e61c62ecf | ||
|
|
fb3868ffaf | ||
|
|
30e80d2440 | ||
|
|
bdbda79043 | ||
|
|
862f2a19f4 | ||
|
|
ae4fd7916c | ||
|
|
26aca867d8 | ||
|
|
138facea62 | ||
|
|
cda04f288e | ||
|
|
85a1aec123 | ||
|
|
9c29d2236d | ||
|
|
df6fe59f72 | ||
|
|
07df9cc46e | ||
|
|
7c8ff36d78 | ||
|
|
111ad3b039 | ||
|
|
8026d0e2f2 | ||
|
|
df1533a1ca | ||
|
|
8639fbae86 | ||
|
|
56e2ad9ee5 | ||
|
|
ce1ddb6c7e | ||
|
|
c5824702bf | ||
|
|
1f148244af | ||
|
|
9faef4aebb | ||
|
|
59f11ecbeb | ||
|
|
750553c866 | ||
|
|
8c964f5ad3 | ||
|
|
90ca13b747 | ||
|
|
f9946127ce | ||
|
|
ddda59a130 | ||
|
|
e981c35863 | ||
|
|
16c4241c0b | ||
|
|
f5e3ff03f0 | ||
|
|
9025d63a63 | ||
|
|
a1de0b96c3 | ||
|
|
ba24a656bf | ||
|
|
caa0cbbc4b | ||
|
|
8c0c5cb671 | ||
|
|
29e9fdba16 | ||
|
|
c665c53cc1 |
44
.github/SECURITY.md
vendored
Normal file
44
.github/SECURITY.md
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# Security Policy
|
||||
|
||||
The maintainers of Docker Compose take security seriously. If you discover
|
||||
a security issue, please bring it to their attention right away!
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Please **DO NOT** file a public issue, instead send your report privately
|
||||
to [security@docker.com](mailto:security@docker.com).
|
||||
|
||||
Reporter(s) can expect a response within 72 hours, acknowledging the issue was
|
||||
received.
|
||||
|
||||
## Review Process
|
||||
|
||||
After receiving the report, an initial triage and technical analysis is
|
||||
performed to confirm the report and determine its scope. We may request
|
||||
additional information in this stage of the process.
|
||||
|
||||
Once a reviewer has confirmed the relevance of the report, a draft security
|
||||
advisory will be created on GitHub. The draft advisory will be used to discuss
|
||||
the issue with maintainers, the reporter(s), and where applicable, other
|
||||
affected parties under embargo.
|
||||
|
||||
If the vulnerability is accepted, a timeline for developing a patch, public
|
||||
disclosure, and patch release will be determined. If there is an embargo period
|
||||
on public disclosure before the patch release, the reporter(s) are expected to
|
||||
participate in the discussion of the timeline and abide by agreed upon dates
|
||||
for public disclosure.
|
||||
|
||||
## Accreditation
|
||||
|
||||
Security reports are greatly appreciated and we will publicly thank you,
|
||||
although we will keep your name confidential if you request it. We also like to
|
||||
send gifts - if you're into swag, make sure to let us know. We do not currently
|
||||
offer a paid security bounty program at this time.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
This project docs not provide long-term supported versions, and only the current
|
||||
release and `main` branch are actively maintained. Docker Compose v1, and the
|
||||
corresponding [v1 branch](https://github.com/docker/compose/tree/v1) reached
|
||||
EOL and are no longer supported.
|
||||
|
||||
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -19,6 +19,5 @@ updates:
|
||||
- dependency-name: "github.com/containerd/containerd"
|
||||
# containerd major/minor must be kept in sync with moby
|
||||
update-types: [ "version-update:semver-major", "version-update:semver-minor" ]
|
||||
- dependency-name: "go.opentelemetry.io/otel/*"
|
||||
# OTEL is v1.x but has some parts that are not API stable yet
|
||||
update-types: [ "version-update:semver-major", "version-update:semver-minor"]
|
||||
# OTEL dependencies should be upgraded in sync with engine, cli, buildkit and buildx projects
|
||||
- dependency-name: "go.opentelemetry.io/*"
|
||||
|
||||
2
.github/stale.yml
vendored
2
.github/stale.yml
vendored
@@ -1,7 +1,7 @@
|
||||
# Configuration for probot-stale - https://github.com/probot/stale
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request becomes stale
|
||||
daysUntilStale: 180
|
||||
daysUntilStale: 90
|
||||
|
||||
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
|
||||
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
|
||||
|
||||
112
.github/workflows/ci.yml
vendored
112
.github/workflows/ci.yml
vendored
@@ -18,9 +18,6 @@ on:
|
||||
required: false
|
||||
default: "false"
|
||||
|
||||
env:
|
||||
DOCKER_CLI_VERSION: "20.10.17"
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
||||
@@ -32,7 +29,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@@ -56,10 +53,10 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Run
|
||||
run: |
|
||||
@@ -81,16 +78,16 @@ jobs:
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v2
|
||||
uses: docker/bake-action@v5
|
||||
with:
|
||||
targets: release
|
||||
set: |
|
||||
@@ -99,10 +96,10 @@ jobs:
|
||||
*.cache-to=type=gha,scope=binary-${{ env.PLATFORM_PAIR }},mode=max
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: compose
|
||||
path: ./bin/release/*
|
||||
name: compose-${{ env.PLATFORM_PAIR }}
|
||||
path: ./bin/release
|
||||
if-no-files-found: error
|
||||
|
||||
test:
|
||||
@@ -110,13 +107,13 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Test
|
||||
uses: docker/bake-action@v2
|
||||
uses: docker/bake-action@v5
|
||||
with:
|
||||
targets: test
|
||||
set: |
|
||||
@@ -124,12 +121,17 @@ jobs:
|
||||
*.cache-to=type=gha,scope=test
|
||||
-
|
||||
name: Gather coverage data
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-data-unit
|
||||
path: bin/coverage/unit/
|
||||
if-no-files-found: error
|
||||
|
||||
-
|
||||
name: Unit Test Summary
|
||||
uses: test-summary/action@v2
|
||||
with:
|
||||
paths: bin/coverage/unit/report.xml
|
||||
if: always()
|
||||
e2e:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
@@ -138,28 +140,40 @@ jobs:
|
||||
mode:
|
||||
- plugin
|
||||
- standalone
|
||||
engine:
|
||||
- 26
|
||||
- 27
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
mode=${{ matrix.mode }}
|
||||
engine=${{ matrix.engine }}
|
||||
echo "MODE_ENGINE_PAIR=${mode}-${engine}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Install Docker ${{ matrix.engine }}
|
||||
run: |
|
||||
sudo systemctl stop docker.service
|
||||
sudo apt-get purge docker-ce docker-ce-cli containerd.io docker-compose-plugin docker-ce-rootless-extras docker-buildx-plugin
|
||||
sudo apt-get install curl
|
||||
curl -fsSL https://test.docker.com -o get-docker.sh
|
||||
sudo sh ./get-docker.sh --version ${{ matrix.engine }}
|
||||
- name: Check Docker Version
|
||||
run: docker --version
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
check-latest: true
|
||||
cache: true
|
||||
-
|
||||
name: Setup docker CLI
|
||||
run: |
|
||||
curl https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKER_CLI_VERSION}.tgz | tar xz
|
||||
sudo cp ./docker/docker /usr/bin/ && rm -rf docker && docker version
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v2
|
||||
uses: docker/bake-action@v5
|
||||
with:
|
||||
targets: binary-with-coverage
|
||||
set: |
|
||||
@@ -185,9 +199,9 @@ jobs:
|
||||
-
|
||||
name: Gather coverage data
|
||||
if: ${{ matrix.mode == 'plugin' }}
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-data-e2e
|
||||
name: coverage-data-e2e-${{ env.MODE_ENGINE_PAIR }}
|
||||
path: bin/coverage/e2e/
|
||||
if-no-files-found: error
|
||||
-
|
||||
@@ -197,36 +211,43 @@ jobs:
|
||||
rm -f /usr/local/bin/docker-compose
|
||||
cp bin/build/docker-compose /usr/local/bin
|
||||
make e2e-compose-standalone
|
||||
|
||||
-
|
||||
name: e2e Test Summary
|
||||
uses: test-summary/action@v2
|
||||
with:
|
||||
paths: /tmp/report/report.xml
|
||||
if: always()
|
||||
coverage:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test
|
||||
- e2e
|
||||
steps:
|
||||
# codecov won't process the report without the source code available
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
check-latest: true
|
||||
- name: Download unit test coverage
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: coverage-data-unit
|
||||
path: coverage/unit
|
||||
merge-multiple: true
|
||||
- name: Download E2E test coverage
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: coverage-data-e2e
|
||||
pattern: coverage-data-e2e-*
|
||||
path: coverage/e2e
|
||||
merge-multiple: true
|
||||
- name: Merge coverage reports
|
||||
run: |
|
||||
go tool covdata textfmt -i=./coverage/unit,./coverage/e2e -o ./coverage.txt
|
||||
- name: Store coverage report in GitHub Actions
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: go-covdata-txt
|
||||
path: ./coverage.txt
|
||||
@@ -246,16 +267,17 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Download artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: compose
|
||||
path: bin/release
|
||||
pattern: compose-*
|
||||
path: ./bin/release
|
||||
merge-multiple: true
|
||||
-
|
||||
name: Create checksums
|
||||
working-directory: bin/release
|
||||
working-directory: ./bin/release
|
||||
run: |
|
||||
find . -type f -print0 | sort -z | xargs -r0 shasum -a 256 -b | sed 's# \*\./# *#' > $RUNNER_TEMP/checksums.txt
|
||||
shasum -a 256 -U -c $RUNNER_TEMP/checksums.txt
|
||||
@@ -263,11 +285,11 @@ jobs:
|
||||
cat checksums.txt | while read sum file; do echo "$sum $file" > ${file#\*}.sha256; done
|
||||
-
|
||||
name: License
|
||||
run: cp packaging/* bin/release/
|
||||
run: cp packaging/* ./bin/release/
|
||||
-
|
||||
name: List artifacts
|
||||
run: |
|
||||
tree -nh bin/release
|
||||
tree -nh ./bin/release
|
||||
-
|
||||
name: Check artifacts
|
||||
run: |
|
||||
@@ -277,7 +299,7 @@ jobs:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: ncipollo/release-action@58ae73b360456532aafd58ee170c045abbeaee37 # v1.10.0
|
||||
with:
|
||||
artifacts: bin/release/*
|
||||
artifacts: ./bin/release/*
|
||||
generateReleaseNotes: true
|
||||
draft: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
58
.github/workflows/codeql.yml
vendored
58
.github/workflows/codeql.yml
vendored
@@ -1,58 +0,0 @@
|
||||
name: codeql
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.txt'
|
||||
- '**/*.yaml'
|
||||
- '**/*_test.go'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- '**/*.txt'
|
||||
- '**/*.yaml'
|
||||
- '**/*_test.go'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: 'ubuntu-latest'
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language:
|
||||
- go
|
||||
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
check-latest: true
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
15
.github/workflows/docs-upstream.yml
vendored
15
.github/workflows/docs-upstream.yml
vendored
@@ -2,6 +2,15 @@
|
||||
# to check if yaml reference docs used in this repo are valid
|
||||
name: docs-upstream
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
@@ -21,21 +30,21 @@ on:
|
||||
|
||||
jobs:
|
||||
docs-yaml:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Upload reference YAML docs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs-yaml
|
||||
path: docs/reference
|
||||
retention-days: 1
|
||||
|
||||
validate:
|
||||
uses: docker/docs/.github/workflows/validate-upstream.yml@main
|
||||
uses: docker/docs/.github/workflows/validate-upstream.yml@919a9b9104a34a40b30d116529bcce589a544d1c # pin for artifact v4 support: https://github.com/docker/docs/pull/19220
|
||||
needs:
|
||||
- docs-yaml
|
||||
with:
|
||||
|
||||
28
.github/workflows/merge.yml
vendored
28
.github/workflows/merge.yml
vendored
@@ -31,9 +31,9 @@ jobs:
|
||||
env:
|
||||
GO111MODULE: "on"
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
@@ -81,17 +81,17 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REPO_SLUG }}
|
||||
@@ -102,13 +102,13 @@ jobs:
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERPUBLICBOT_USERNAME }}
|
||||
password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }}
|
||||
-
|
||||
name: Build and push image
|
||||
uses: docker/bake-action@v2
|
||||
uses: docker/bake-action@v5
|
||||
id: bake
|
||||
with:
|
||||
files: |
|
||||
@@ -129,14 +129,16 @@ jobs:
|
||||
-
|
||||
name: Generate Token
|
||||
id: generate_token
|
||||
uses: tibdex/github-app-token@v1
|
||||
uses: actions/create-github-app-token@v1
|
||||
with:
|
||||
app_id: ${{ vars.DOCKERDESKTOP_APP_ID }}
|
||||
private_key: ${{ secrets.DOCKERDESKTOP_APP_PRIVATEKEY }}
|
||||
repository: docker/${{ secrets.DOCKERDESKTOP_REPO }}
|
||||
app-id: ${{ vars.DOCKERDESKTOP_APP_ID }}
|
||||
private-key: ${{ secrets.DOCKERDESKTOP_APP_PRIVATEKEY }}
|
||||
owner: docker
|
||||
repositories: |
|
||||
${{ secrets.DOCKERDESKTOP_REPO }}
|
||||
-
|
||||
name: Trigger Docker Desktop e2e with edge version
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ steps.generate_token.outputs.token }}
|
||||
script: |
|
||||
|
||||
8
.github/workflows/scorecards.yml
vendored
8
.github/workflows/scorecards.yml
vendored
@@ -22,12 +22,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846 # tag=v3.0.0
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # tag=v4.4.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@99c53751e09b9529366343771cc321ec74e9bd3d # tag=v2.0.6
|
||||
uses: ossf/scorecard-action@62b2cac7ed8198b15735ed49ab1e5cf35480ba46 # tag=v2.4.0
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535 # tag=v3.0.0
|
||||
uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # tag=v4.5.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
@@ -49,6 +49,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@5f532563584d71fdef14ee64d17bafb34f751ce5 # tag=v1.0.26
|
||||
uses: github/codeql-action/upload-sarif@3096afedf9873361b2b2f65e1445b13272c83eb8 # tag=v2.20.00
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
33
.github/workflows/stale.yml
vendored
Normal file
33
.github/workflows/stale.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: 'Close stale issues'
|
||||
|
||||
# Default to 'contents: read', which grants actions to read commits.
|
||||
#
|
||||
# If any permission is set, any permission not included in the list is
|
||||
# implicitly set to "none".
|
||||
#
|
||||
# see https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * 0,3' # at midnight UTC every Sunday and Wednesday
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
stale-issue-message: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
days-before-issue-stale: 150 # marks stale after 5 months
|
||||
days-before-issue-close: 30 # closes 1 month after being marked with no action
|
||||
stale-issue-label: "stale"
|
||||
exempt-issue-labels: "kind/feature,kind/enhancement"
|
||||
|
||||
@@ -10,7 +10,7 @@ linters:
|
||||
- errorlint
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- gofumpt
|
||||
- goimports
|
||||
- gomodguard
|
||||
- revive
|
||||
@@ -22,6 +22,7 @@ linters:
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- staticcheck
|
||||
- testifylint
|
||||
- typecheck
|
||||
- unconvert
|
||||
- unparam
|
||||
@@ -69,5 +70,13 @@ linters-settings:
|
||||
line-length: 200
|
||||
issues:
|
||||
# golangci hides some golint warnings (the warning about exported things
|
||||
# withtout documentation for example), this will make it show them anyway.
|
||||
# without documentation for example), this will make it show them anyway.
|
||||
exclude-use-default: false
|
||||
# Maximum issues count per one linter.
|
||||
# Set to 0 to disable.
|
||||
# Default: 50
|
||||
max-issues-per-linter: 0
|
||||
# Maximum count of issues with the same text.
|
||||
# Set to 0 to disable.
|
||||
# Default: 3
|
||||
max-same-issues: 0
|
||||
|
||||
@@ -49,7 +49,7 @@ To execute both CLI and standalone e2e tests, run :
|
||||
make e2e
|
||||
```
|
||||
|
||||
Or if you need to build the CLI, run:
|
||||
Or if you need to build the CLI, run:
|
||||
```console
|
||||
make build-and-e2e
|
||||
```
|
||||
@@ -85,7 +85,7 @@ make build-and-e2e-compose-standalone
|
||||
|
||||
To create a new release:
|
||||
* Check that the CI is green on the main branch for the commit you want to release
|
||||
* Run the release Github Actions workflow with a tag of form vx.y.z following existing tags.
|
||||
* Run the release GitHub Actions workflow with a tag of form vx.y.z following existing tags.
|
||||
|
||||
This will automatically create a new tag, release and make binaries for
|
||||
Windows, macOS, and Linux available for download on the
|
||||
|
||||
@@ -66,6 +66,11 @@ When sending lengthy log files, consider posting them as a gist
|
||||
Don't forget to remove sensitive data from your log files before posting (you
|
||||
can replace those parts with "REDACTED").
|
||||
|
||||
_Note:_
|
||||
Maintainers might request additional information to diagnose an issue,
|
||||
if initial reporter doesn't answer within a reasonable delay (a few weeks),
|
||||
issue will be closed.
|
||||
|
||||
## Quick contribution tips and guidelines
|
||||
|
||||
This section gives the experienced contributor some tips and guidelines.
|
||||
@@ -80,8 +85,7 @@ before anybody starts working on it.
|
||||
|
||||
We are always thrilled to receive pull requests. We do our best to process them
|
||||
quickly. If your pull request is not accepted on the first try,
|
||||
don't get discouraged! Our contributor's guide explains
|
||||
[the review process we use for simple changes](https://docs.docker.com/opensource/workflow/make-a-contribution/).
|
||||
don't get discouraged!
|
||||
|
||||
### Talking to other Docker users and contributors
|
||||
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -15,9 +15,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.21.4
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG GOLANGCI_LINT_VERSION=v1.54.2
|
||||
ARG GO_VERSION=1.22.10
|
||||
ARG XX_VERSION=1.6.1
|
||||
ARG GOLANGCI_LINT_VERSION=v1.60.2
|
||||
ARG ADDLICENSE_VERSION=v1.0.0
|
||||
|
||||
ARG BUILD_TAGS="e2e"
|
||||
@@ -106,11 +106,14 @@ RUN --mount=type=bind,target=. \
|
||||
--mount=type=cache,target=/go/pkg/mod \
|
||||
rm -rf /tmp/coverage && \
|
||||
mkdir -p /tmp/coverage && \
|
||||
go test -tags "$BUILD_TAGS" -v -cover -covermode=atomic $(go list $(TAGS) ./... | grep -vE 'e2e') -args -test.gocoverdir="/tmp/coverage" && \
|
||||
rm -rf /tmp/report && \
|
||||
mkdir -p /tmp/report && \
|
||||
go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -tags "$BUILD_TAGS" -v -cover -covermode=atomic $(go list $(TAGS) ./... | grep -vE 'e2e') -args -test.gocoverdir="/tmp/coverage" && \
|
||||
go tool covdata percent -i=/tmp/coverage
|
||||
|
||||
FROM scratch AS test-coverage
|
||||
COPY --from=test --link /tmp/coverage /
|
||||
COPY --from=test --link /tmp/report /
|
||||
|
||||
FROM base AS license-set
|
||||
ARG LICENSE_FILES
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
people = [
|
||||
"glours",
|
||||
"jhrotko",
|
||||
"milas",
|
||||
"laurazard",
|
||||
"ndeloof",
|
||||
"nicksieger",
|
||||
"StefanScherer",
|
||||
@@ -39,6 +39,7 @@
|
||||
"aiordache",
|
||||
"chris-crone",
|
||||
"gtardif",
|
||||
"laurazard",
|
||||
"maxcleme",
|
||||
"rumpl",
|
||||
"thaJeztah"
|
||||
@@ -72,6 +73,11 @@
|
||||
Email = "guillaume.tardif@docker.com"
|
||||
GitHub = "gtardif"
|
||||
|
||||
[people.jhrotko]
|
||||
Name = "Joana Hrotko"
|
||||
Email = "joana.hrotko@docker.com"
|
||||
Github = "jhrotko"
|
||||
|
||||
[people.laurazard]
|
||||
Name = "Laura Brehm"
|
||||
Email = "laura.brehm@docker.com"
|
||||
|
||||
11
Makefile
11
Makefile
@@ -75,11 +75,11 @@ install: binary
|
||||
|
||||
.PHONY: e2e-compose
|
||||
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
go test -v $(TEST_FLAGS) -count=1 ./pkg/e2e
|
||||
go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -v $(TEST_FLAGS) -count=1 ./pkg/e2e
|
||||
|
||||
.PHONY: e2e-compose-standalone
|
||||
e2e-compose-standalone: ## Run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
|
||||
go test $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
|
||||
go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- $(TEST_FLAGS) -v -count=1 -parallel=1 --tags=standalone ./pkg/e2e
|
||||
|
||||
.PHONY: build-and-e2e-compose
|
||||
build-and-e2e-compose: build e2e-compose ## Compile the compose cli-plugin and run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
@@ -89,7 +89,7 @@ build-and-e2e-compose-standalone: build e2e-compose-standalone ## Compile the co
|
||||
|
||||
.PHONY: mocks
|
||||
mocks:
|
||||
mockgen --version >/dev/null 2>&1 || go install github.com/golang/mock/mockgen@v1.6.0
|
||||
mockgen --version >/dev/null 2>&1 || go install go.uber.org/mock/mockgen@v0.4.0
|
||||
mockgen -destination pkg/mocks/mock_docker_cli.go -package mocks github.com/docker/cli/cli/command Cli
|
||||
mockgen -destination pkg/mocks/mock_docker_api.go -package mocks github.com/docker/docker/client APIClient
|
||||
mockgen -destination pkg/mocks/mock_docker_compose_api.go -package mocks -source=./pkg/api/api.go Service
|
||||
@@ -116,6 +116,11 @@ cache-clear: ## Clear the builder cache
|
||||
lint: ## run linter(s)
|
||||
$(BUILDX_CMD) bake lint
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
gofumpt --version >/dev/null 2>&1 || go install mvdan.cc/gofumpt@latest
|
||||
gofumpt -w .
|
||||
|
||||
.PHONY: docs
|
||||
docs: ## generate documentation
|
||||
$(eval $@_TMP_OUT := $(shell mktemp -d -t compose-output.XXXXXXXXXX))
|
||||
|
||||
@@ -52,7 +52,7 @@ Quick Start
|
||||
Using Docker Compose is a three-step process:
|
||||
1. Define your app's environment with a `Dockerfile` so it can be
|
||||
reproduced anywhere.
|
||||
2. Define the services that make up your app in `docker-compose.yml` so
|
||||
2. Define the services that make up your app in `compose.yaml` so
|
||||
they can be run together in an isolated environment.
|
||||
3. Lastly, run `docker compose up` and Compose will start and run your entire
|
||||
app.
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
"go.opentelemetry.io/otel"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
@@ -50,7 +51,7 @@ func Setup(cmd *cobra.Command, dockerCli command.Cli, args []string) error {
|
||||
}
|
||||
|
||||
ctx := cmd.Context()
|
||||
ctx, cmdSpan := tracing.Tracer.Start(
|
||||
ctx, cmdSpan := otel.Tracer("").Start(
|
||||
ctx,
|
||||
"cli/"+strings.Join(commandName(cmd), "-"),
|
||||
)
|
||||
@@ -104,7 +105,7 @@ func wrapRunE(c *cobra.Command, cmdSpan trace.Span, tracingShutdown tracing.Shut
|
||||
if tracingShutdown != nil {
|
||||
// use background for root context because the cmd's context might have
|
||||
// been canceled already
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
|
||||
defer cancel()
|
||||
// TODO(milas): add an env var to enable logging from the
|
||||
// OTel components for debugging purposes
|
||||
|
||||
@@ -60,5 +60,4 @@ func TestGetFlags(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ package compatibility
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/compose"
|
||||
)
|
||||
@@ -55,13 +56,14 @@ func Convert(args []string) []string {
|
||||
var rootFlags []string
|
||||
command := []string{compose.PluginName}
|
||||
l := len(args)
|
||||
ARGS:
|
||||
for i := 0; i < l; i++ {
|
||||
arg := args[i]
|
||||
if contains(getCompletionCommands(), arg) {
|
||||
command = append([]string{arg}, command...)
|
||||
continue
|
||||
}
|
||||
if len(arg) > 0 && arg[0] != '-' {
|
||||
if arg != "" && arg[0] != '-' {
|
||||
command = append(command, args[i:]...)
|
||||
break
|
||||
}
|
||||
@@ -81,14 +83,23 @@ func Convert(args []string) []string {
|
||||
rootFlags = append(rootFlags, arg)
|
||||
continue
|
||||
}
|
||||
if contains(getStringFlags(), arg) {
|
||||
i++
|
||||
if i >= l {
|
||||
fmt.Fprintf(os.Stderr, "flag needs an argument: '%s'\n", arg)
|
||||
os.Exit(1)
|
||||
for _, flag := range getStringFlags() {
|
||||
if arg == flag {
|
||||
i++
|
||||
if i >= l {
|
||||
fmt.Fprintf(os.Stderr, "flag needs an argument: '%s'\n", arg)
|
||||
os.Exit(1)
|
||||
}
|
||||
rootFlags = append(rootFlags, arg, args[i])
|
||||
continue ARGS
|
||||
}
|
||||
if strings.HasPrefix(arg, flag) {
|
||||
_, val, found := strings.Cut(arg, "=")
|
||||
if found {
|
||||
rootFlags = append(rootFlags, flag, val)
|
||||
continue ARGS
|
||||
}
|
||||
}
|
||||
rootFlags = append(rootFlags, arg, args[i])
|
||||
continue
|
||||
}
|
||||
command = append(command, arg)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,11 @@ func Test_convert(t *testing.T) {
|
||||
args: []string{"--context", "foo", "-f", "compose.yaml", "up"},
|
||||
want: []string{"--context", "foo", "compose", "-f", "compose.yaml", "up"},
|
||||
},
|
||||
{
|
||||
name: "with context arg",
|
||||
args: []string{"--context=foo", "-f", "compose.yaml", "up"},
|
||||
want: []string{"--context", "foo", "compose", "-f", "compose.yaml", "up"},
|
||||
},
|
||||
{
|
||||
name: "with host",
|
||||
args: []string{"--host", "tcp://1.2.3.4", "up"},
|
||||
|
||||
@@ -31,9 +31,9 @@ func alphaCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
watchCommand(p, dockerCli, backend),
|
||||
vizCommand(p, dockerCli, backend),
|
||||
publishCommand(p, dockerCli, backend),
|
||||
generateCommand(p, backend),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
80
cmd/compose/attach.go
Normal file
80
cmd/compose/attach.go
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type attachOpts struct {
|
||||
*composeOptions
|
||||
|
||||
service string
|
||||
index int
|
||||
|
||||
detachKeys string
|
||||
noStdin bool
|
||||
proxy bool
|
||||
}
|
||||
|
||||
func attachCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
opts := attachOpts{
|
||||
composeOptions: &composeOptions{
|
||||
ProjectOptions: p,
|
||||
},
|
||||
}
|
||||
runCmd := &cobra.Command{
|
||||
Use: "attach [OPTIONS] SERVICE",
|
||||
Short: "Attach local standard input, output, and error streams to a service's running container",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
opts.service = args[0]
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runAttach(ctx, dockerCli, backend, opts)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
runCmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas.")
|
||||
runCmd.Flags().StringVarP(&opts.detachKeys, "detach-keys", "", "", "Override the key sequence for detaching from a container.")
|
||||
|
||||
runCmd.Flags().BoolVar(&opts.noStdin, "no-stdin", false, "Do not attach STDIN")
|
||||
runCmd.Flags().BoolVar(&opts.proxy, "sig-proxy", true, "Proxy all received signals to the process")
|
||||
return runCmd
|
||||
}
|
||||
|
||||
func runAttach(ctx context.Context, dockerCli command.Cli, backend api.Service, opts attachOpts) error {
|
||||
projectName, err := opts.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
attachOpts := api.AttachOptions{
|
||||
Service: opts.service,
|
||||
Index: opts.index,
|
||||
DetachKeys: opts.detachKeys,
|
||||
NoStdin: opts.noStdin,
|
||||
Proxy: opts.proxy,
|
||||
}
|
||||
return backend.Attach(ctx, projectName, attachOpts)
|
||||
}
|
||||
@@ -22,13 +22,12 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
buildx "github.com/docker/buildx/util/progress"
|
||||
"github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliopts "github.com/docker/cli/opts"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
buildkit "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -44,30 +43,39 @@ type buildOptions struct {
|
||||
memory cliopts.MemBytes
|
||||
ssh string
|
||||
builder string
|
||||
deps bool
|
||||
}
|
||||
|
||||
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
|
||||
var SSHKeys []types.SSHKey
|
||||
var err error
|
||||
if opts.ssh != "" {
|
||||
SSHKeys, err = loader.ParseShortSSHSyntax(opts.ssh)
|
||||
if err != nil {
|
||||
return api.BuildOptions{}, err
|
||||
id, path, found := strings.Cut(opts.ssh, "=")
|
||||
if !found && id != "default" {
|
||||
return api.BuildOptions{}, fmt.Errorf("invalid ssh key %q", opts.ssh)
|
||||
}
|
||||
SSHKeys = append(SSHKeys, types.SSHKey{
|
||||
ID: id,
|
||||
Path: path,
|
||||
})
|
||||
}
|
||||
builderName := opts.builder
|
||||
if builderName == "" {
|
||||
builderName = os.Getenv("BUILDX_BUILDER")
|
||||
}
|
||||
|
||||
uiMode := ui.Mode
|
||||
if uiMode == ui.ModeJSON {
|
||||
uiMode = "rawjson"
|
||||
}
|
||||
return api.BuildOptions{
|
||||
Pull: opts.pull,
|
||||
Push: opts.push,
|
||||
Progress: ui.Mode,
|
||||
Progress: uiMode,
|
||||
Args: types.NewMappingWithEquals(opts.args),
|
||||
NoCache: opts.noCache,
|
||||
Quiet: opts.quiet,
|
||||
Services: services,
|
||||
Deps: opts.deps,
|
||||
SSHs: SSHKeys,
|
||||
Builder: builderName,
|
||||
}, nil
|
||||
@@ -102,30 +110,33 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
cmd.Flags().BoolVar(&opts.push, "push", false, "Push service images.")
|
||||
cmd.Flags().BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
|
||||
cmd.Flags().BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image.")
|
||||
cmd.Flags().StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services.")
|
||||
cmd.Flags().StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
|
||||
cmd.Flags().StringVar(&opts.builder, "builder", "", "Set builder to use.")
|
||||
cmd.Flags().Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("parallel") //nolint:errcheck
|
||||
cmd.Flags().Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("compress") //nolint:errcheck
|
||||
cmd.Flags().Bool("force-rm", true, "Always remove intermediate containers. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("force-rm") //nolint:errcheck
|
||||
cmd.Flags().BoolVar(&opts.noCache, "no-cache", false, "Do not use cache when building the image")
|
||||
cmd.Flags().Bool("no-rm", false, "Do not remove intermediate containers after a successful build. DEPRECATED")
|
||||
cmd.Flags().MarkHidden("no-rm") //nolint:errcheck
|
||||
cmd.Flags().VarP(&opts.memory, "memory", "m", "Set memory limit for the build container. Not supported by BuildKit.")
|
||||
cmd.Flags().StringVar(&p.Progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
|
||||
cmd.Flags().MarkHidden("progress") //nolint:errcheck
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.push, "push", false, "Push service images")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
|
||||
flags.BoolVar(&opts.pull, "pull", false, "Always attempt to pull a newer version of the image")
|
||||
flags.StringArrayVar(&opts.args, "build-arg", []string{}, "Set build-time variables for services")
|
||||
flags.StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
|
||||
flags.StringVar(&opts.builder, "builder", "", "Set builder to use")
|
||||
flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively)")
|
||||
|
||||
flags.Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
||||
flags.MarkHidden("parallel") //nolint:errcheck
|
||||
flags.Bool("compress", true, "Compress the build context using gzip. DEPRECATED")
|
||||
flags.MarkHidden("compress") //nolint:errcheck
|
||||
flags.Bool("force-rm", true, "Always remove intermediate containers. DEPRECATED")
|
||||
flags.MarkHidden("force-rm") //nolint:errcheck
|
||||
flags.BoolVar(&opts.noCache, "no-cache", false, "Do not use cache when building the image")
|
||||
flags.Bool("no-rm", false, "Do not remove intermediate containers after a successful build. DEPRECATED")
|
||||
flags.MarkHidden("no-rm") //nolint:errcheck
|
||||
flags.VarP(&opts.memory, "memory", "m", "Set memory limit for the build container. Not supported by BuildKit.")
|
||||
flags.StringVar(&p.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
|
||||
flags.MarkHidden("progress") //nolint:errcheck
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error {
|
||||
project, err := opts.ToProject(dockerCli, services, cli.WithResolvedPaths(true))
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
93
cmd/compose/commit.go
Normal file
93
cmd/compose/commit.go
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type commitOptions struct {
|
||||
*ProjectOptions
|
||||
|
||||
service string
|
||||
reference string
|
||||
|
||||
pause bool
|
||||
comment string
|
||||
author string
|
||||
changes opts.ListOpts
|
||||
|
||||
index int
|
||||
}
|
||||
|
||||
func commitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
options := commitOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "commit [OPTIONS] SERVICE [REPOSITORY[:TAG]]",
|
||||
Short: "Create a new image from a service container's changes",
|
||||
Args: cobra.RangeArgs(1, 2),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
options.service = args[0]
|
||||
if len(args) > 1 {
|
||||
options.reference = args[1]
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runCommit(ctx, dockerCli, backend, options)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.IntVar(&options.index, "index", 0, "index of the container if service has multiple replicas.")
|
||||
|
||||
flags.BoolVarP(&options.pause, "pause", "p", true, "Pause container during commit")
|
||||
flags.StringVarP(&options.comment, "message", "m", "", "Commit message")
|
||||
flags.StringVarP(&options.author, "author", "a", "", `Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")`)
|
||||
options.changes = opts.NewListOpts(nil)
|
||||
flags.VarP(&options.changes, "change", "c", "Apply Dockerfile instruction to the created image")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCommit(ctx context.Context, dockerCli command.Cli, backend api.Service, options commitOptions) error {
|
||||
projectName, err := options.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commitOptions := api.CommitOptions{
|
||||
Service: options.service,
|
||||
Reference: options.reference,
|
||||
Pause: options.pause,
|
||||
Comment: options.comment,
|
||||
Author: options.author,
|
||||
Changes: options.changes,
|
||||
Index: options.index,
|
||||
}
|
||||
|
||||
return backend.Commit(ctx, projectName, commitOptions)
|
||||
}
|
||||
@@ -37,17 +37,18 @@ func noCompletion() validArgsFn {
|
||||
func completeServiceNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
p.Offline = true
|
||||
project, err := p.ToProject(dockerCli, nil)
|
||||
project, _, err := p.ToProject(cmd.Context(), dockerCli, nil)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
var serviceNames []string
|
||||
for _, s := range project.ServiceNames() {
|
||||
var values []string
|
||||
serviceNames := append(project.ServiceNames(), project.DisabledServiceNames()...)
|
||||
for _, s := range serviceNames {
|
||||
if toComplete == "" || strings.HasPrefix(s, toComplete) {
|
||||
serviceNames = append(serviceNames, s)
|
||||
values = append(values, s)
|
||||
}
|
||||
}
|
||||
return serviceNames, cobra.ShellCompDirectiveNoFileComp
|
||||
return values, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +73,7 @@ func completeProjectNames(backend api.Service) func(cmd *cobra.Command, args []s
|
||||
func completeProfileNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
p.Offline = true
|
||||
project, err := p.ToProject(dockerCli, nil)
|
||||
project, _, err := p.ToProject(cmd.Context(), dockerCli, nil)
|
||||
if err != nil {
|
||||
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
@@ -27,26 +29,30 @@ import (
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/dotenv"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
composegoutils "github.com/compose-spec/compose-go/utils"
|
||||
"github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/dotenv"
|
||||
"github.com/compose-spec/compose-go/v2/loader"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
composegoutils "github.com/compose-spec/compose-go/v2/utils"
|
||||
"github.com/docker/buildx/util/logutil"
|
||||
buildx "github.com/docker/buildx/util/progress"
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/pkg/kvfile"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/internal/desktop"
|
||||
"github.com/docker/compose/v2/internal/experimental"
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/remote"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
buildkit "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -56,14 +62,44 @@ const (
|
||||
ComposeProjectName = "COMPOSE_PROJECT_NAME"
|
||||
// ComposeCompatibility try to mimic compose v1 as much as possible
|
||||
ComposeCompatibility = "COMPOSE_COMPATIBILITY"
|
||||
// ComposeRemoveOrphans remove “orphaned" containers, i.e. containers tagged for current project but not declared as service
|
||||
// ComposeRemoveOrphans remove "orphaned" containers, i.e. containers tagged for current project but not declared as service
|
||||
ComposeRemoveOrphans = "COMPOSE_REMOVE_ORPHANS"
|
||||
// ComposeIgnoreOrphans ignore "orphaned" containers
|
||||
ComposeIgnoreOrphans = "COMPOSE_IGNORE_ORPHANS"
|
||||
// ComposeEnvFiles defines the env files to use if --env-file isn't used
|
||||
ComposeEnvFiles = "COMPOSE_ENV_FILES"
|
||||
// ComposeMenu defines if the navigation menu should be rendered. Can be also set via --menu
|
||||
ComposeMenu = "COMPOSE_MENU"
|
||||
)
|
||||
|
||||
// rawEnv load a dot env file using docker/cli key=value parser, without attempt to interpolate or evaluate values
|
||||
func rawEnv(r io.Reader, filename string, lookup func(key string) (string, bool)) (map[string]string, error) {
|
||||
lines, err := kvfile.ParseFromReader(r, lookup)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse env_file %s: %w", filename, err)
|
||||
}
|
||||
vars := types.Mapping{}
|
||||
for _, line := range lines {
|
||||
key, value, _ := strings.Cut(line, "=")
|
||||
vars[key] = value
|
||||
}
|
||||
return vars, nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
// compose evaluates env file values for interpolation
|
||||
// `raw` format allows to load env_file with the same parser used by docker run --env-file
|
||||
dotenv.RegisterFormat("raw", rawEnv)
|
||||
}
|
||||
|
||||
type Backend interface {
|
||||
api.Service
|
||||
|
||||
SetDesktopClient(cli *desktop.Client)
|
||||
|
||||
SetExperiments(experiments *experimental.State)
|
||||
}
|
||||
|
||||
// Command defines a compose CLI command as a func with args
|
||||
type Command func(context.Context, []string) error
|
||||
|
||||
@@ -73,20 +109,17 @@ type CobraCommand func(context.Context, *cobra.Command, []string) error
|
||||
// AdaptCmd adapt a CobraCommand func to cobra library
|
||||
func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
|
||||
return func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
contextString := fmt.Sprintf("%s", ctx)
|
||||
if !strings.HasSuffix(contextString, ".WithCancel") { // need to handle cancel
|
||||
cancellableCtx, cancel := context.WithCancel(cmd.Context())
|
||||
ctx = cancellableCtx
|
||||
s := make(chan os.Signal, 1)
|
||||
signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
|
||||
go func() {
|
||||
<-s
|
||||
cancel()
|
||||
signal.Stop(s)
|
||||
close(s)
|
||||
}()
|
||||
}
|
||||
ctx, cancel := context.WithCancel(cmd.Context())
|
||||
|
||||
s := make(chan os.Signal, 1)
|
||||
signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
|
||||
go func() {
|
||||
<-s
|
||||
cancel()
|
||||
signal.Stop(s)
|
||||
close(s)
|
||||
}()
|
||||
|
||||
err := fn(ctx, cmd, args)
|
||||
var composeErr compose.Error
|
||||
if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
|
||||
@@ -101,6 +134,9 @@ func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
|
||||
Status: err.Error(),
|
||||
}
|
||||
}
|
||||
if ui.Mode == ui.ModeJSON {
|
||||
err = makeJSONError(err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -122,6 +158,7 @@ type ProjectOptions struct {
|
||||
Compatibility bool
|
||||
Progress string
|
||||
Offline bool
|
||||
All bool
|
||||
}
|
||||
|
||||
// ProjectFunc does stuff within a types.Project
|
||||
@@ -143,35 +180,76 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF
|
||||
options := []cli.ProjectOptionsFn{
|
||||
cli.WithResolvedPaths(true),
|
||||
cli.WithDiscardEnvFile,
|
||||
cli.WithContext(ctx),
|
||||
}
|
||||
|
||||
project, err := o.ToProject(dockerCli, args, options...)
|
||||
project, metrics, err := o.ToProject(ctx, dockerCli, args, options...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, tracing.MetricsKey{}, metrics)
|
||||
|
||||
return fn(ctx, project, args)
|
||||
})
|
||||
}
|
||||
|
||||
type jsonErrorData struct {
|
||||
Error bool `json:"error,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func errorAsJSON(message string) string {
|
||||
errorMessage := &jsonErrorData{
|
||||
Error: true,
|
||||
Message: message,
|
||||
}
|
||||
marshal, err := json.Marshal(errorMessage)
|
||||
if err == nil {
|
||||
return string(marshal)
|
||||
} else {
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
func makeJSONError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
var statusErr dockercli.StatusError
|
||||
if errors.As(err, &statusErr) {
|
||||
return dockercli.StatusError{
|
||||
StatusCode: statusErr.StatusCode,
|
||||
Status: errorAsJSON(statusErr.Status),
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%s", errorAsJSON(err.Error()))
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
f.StringArrayVar(&o.Profiles, "profile", []string{}, "Specify a profile to enable")
|
||||
f.StringVarP(&o.ProjectName, "project-name", "p", "", "Project name")
|
||||
f.StringArrayVarP(&o.ConfigPaths, "file", "f", []string{}, "Compose configuration files")
|
||||
f.StringArrayVar(&o.EnvFiles, "env-file", nil, "Specify an alternate environment file.")
|
||||
f.StringArrayVar(&o.EnvFiles, "env-file", defaultStringArrayVar(ComposeEnvFiles), "Specify an alternate environment file")
|
||||
f.StringVar(&o.ProjectDir, "project-directory", "", "Specify an alternate working directory\n(default: the path of the, first specified, Compose file)")
|
||||
f.StringVar(&o.WorkDir, "workdir", "", "DEPRECATED! USE --project-directory INSTEAD.\nSpecify an alternate working directory\n(default: the path of the, first specified, Compose file)")
|
||||
f.BoolVar(&o.Compatibility, "compatibility", false, "Run compose in backward compatibility mode")
|
||||
f.StringVar(&o.Progress, "progress", buildx.PrinterModeAuto, fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
f.StringVar(&o.Progress, "progress", string(buildkit.AutoMode), fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
f.BoolVar(&o.All, "all-resources", false, "Include all resources, even those not used by services")
|
||||
_ = f.MarkHidden("workdir")
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) projectOrName(dockerCli command.Cli, services ...string) (*types.Project, string, error) {
|
||||
// get default value for a command line flag that is set by a coma-separated value in environment variable
|
||||
func defaultStringArrayVar(env string) []string {
|
||||
return strings.FieldsFunc(os.Getenv(env), func(c rune) bool {
|
||||
return c == ','
|
||||
})
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cli, services ...string) (*types.Project, string, error) {
|
||||
name := o.ProjectName
|
||||
var project *types.Project
|
||||
if len(o.ConfigPaths) > 0 || o.ProjectName == "" {
|
||||
p, err := o.ToProject(dockerCli, services, cli.WithDiscardEnvFile)
|
||||
p, _, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile)
|
||||
if err != nil {
|
||||
envProjectName := os.Getenv(ComposeProjectName)
|
||||
if envProjectName != "" {
|
||||
@@ -185,7 +263,7 @@ func (o *ProjectOptions) projectOrName(dockerCli command.Cli, services ...string
|
||||
return project, name, nil
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) toProjectName(dockerCli command.Cli) (string, error) {
|
||||
func (o *ProjectOptions) toProjectName(ctx context.Context, dockerCli command.Cli) (string, error) {
|
||||
if o.ProjectName != "" {
|
||||
return o.ProjectName, nil
|
||||
}
|
||||
@@ -195,45 +273,88 @@ func (o *ProjectOptions) toProjectName(dockerCli command.Cli) (string, error) {
|
||||
return envProjectName, nil
|
||||
}
|
||||
|
||||
project, err := o.ToProject(dockerCli, nil)
|
||||
project, _, err := o.ToProject(ctx, dockerCli, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return project.Name, nil
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) ToProject(dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
|
||||
if !o.Offline {
|
||||
po = o.configureRemoteLoaders(dockerCli, po)
|
||||
func (o *ProjectOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) {
|
||||
remotes := o.remoteLoaders(dockerCli)
|
||||
for _, r := range remotes {
|
||||
po = append(po, cli.WithResourceLoader(r))
|
||||
}
|
||||
|
||||
options, err := o.toProjectOptions(po...)
|
||||
if err != nil {
|
||||
return nil, compose.WrapComposeError(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) {
|
||||
api.Separator = "_"
|
||||
}
|
||||
|
||||
project, err := cli.ProjectFromOptions(options)
|
||||
return options.LoadModel(ctx)
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, tracing.Metrics, error) { //nolint:gocyclo
|
||||
var metrics tracing.Metrics
|
||||
remotes := o.remoteLoaders(dockerCli)
|
||||
for _, r := range remotes {
|
||||
po = append(po, cli.WithResourceLoader(r))
|
||||
}
|
||||
|
||||
options, err := o.toProjectOptions(po...)
|
||||
if err != nil {
|
||||
return nil, compose.WrapComposeError(err)
|
||||
return nil, metrics, compose.WrapComposeError(err)
|
||||
}
|
||||
|
||||
options.WithListeners(func(event string, metadata map[string]any) {
|
||||
switch event {
|
||||
case "extends":
|
||||
metrics.CountExtends++
|
||||
case "include":
|
||||
paths := metadata["path"].(types.StringList)
|
||||
for _, path := range paths {
|
||||
var isRemote bool
|
||||
for _, r := range remotes {
|
||||
if r.Accept(path) {
|
||||
isRemote = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if isRemote {
|
||||
metrics.CountIncludesRemote++
|
||||
} else {
|
||||
metrics.CountIncludesLocal++
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if o.Compatibility || utils.StringToBool(options.Environment[ComposeCompatibility]) {
|
||||
api.Separator = "_"
|
||||
}
|
||||
|
||||
project, err := options.LoadProject(ctx)
|
||||
if err != nil {
|
||||
return nil, metrics, compose.WrapComposeError(err)
|
||||
}
|
||||
|
||||
if project.Name == "" {
|
||||
return nil, errors.New("project name can't be empty. Use `--project-name` to set a valid name")
|
||||
return nil, metrics, errors.New("project name can't be empty. Use `--project-name` to set a valid name")
|
||||
}
|
||||
|
||||
err = project.EnableServices(services...)
|
||||
project, err = project.WithServicesEnabled(services...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, metrics, err
|
||||
}
|
||||
|
||||
for i, s := range project.Services {
|
||||
for name, s := range project.Services {
|
||||
s.CustomLabels = map[string]string{
|
||||
api.ProjectLabel: project.Name,
|
||||
api.ServiceLabel: s.Name,
|
||||
api.ServiceLabel: name,
|
||||
api.VersionLabel: api.ComposeVersion,
|
||||
api.WorkingDirLabel: project.WorkingDir,
|
||||
api.ConfigFilesLabel: strings.Join(project.ComposeFiles, ","),
|
||||
@@ -242,32 +363,47 @@ func (o *ProjectOptions) ToProject(dockerCli command.Cli, services []string, po
|
||||
if len(o.EnvFiles) != 0 {
|
||||
s.CustomLabels[api.EnvironmentFileLabel] = strings.Join(o.EnvFiles, ",")
|
||||
}
|
||||
project.Services[i] = s
|
||||
project.Services[name] = s
|
||||
}
|
||||
|
||||
project.WithoutUnnecessaryResources()
|
||||
project, err = project.WithSelectedServices(services)
|
||||
if err != nil {
|
||||
return nil, tracing.Metrics{}, err
|
||||
}
|
||||
|
||||
err = project.ForServices(services)
|
||||
return project, err
|
||||
if !o.All {
|
||||
project = project.WithoutUnnecessaryResources()
|
||||
}
|
||||
return project, metrics, err
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) configureRemoteLoaders(dockerCli command.Cli, po []cli.ProjectOptionsFn) []cli.ProjectOptionsFn {
|
||||
func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceLoader {
|
||||
if o.Offline {
|
||||
return nil
|
||||
}
|
||||
git := remote.NewGitRemoteLoader(o.Offline)
|
||||
oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline)
|
||||
|
||||
po = append(po, cli.WithResourceLoader(git), cli.WithResourceLoader(oci))
|
||||
return po
|
||||
return []loader.ResourceLoader{git, oci}
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
|
||||
return cli.NewProjectOptions(o.ConfigPaths,
|
||||
append(po,
|
||||
cli.WithWorkingDirectory(o.ProjectDir),
|
||||
// First apply os.Environment, always win
|
||||
cli.WithOsEnv,
|
||||
// Load PWD/.env if present and no explicit --env-file has been set
|
||||
cli.WithEnvFiles(o.EnvFiles...),
|
||||
// read dot env file to populate project environment
|
||||
cli.WithDotEnv,
|
||||
// get compose file path set by COMPOSE_FILE
|
||||
cli.WithConfigFileEnv,
|
||||
// if none was selected, get default compose.yaml file from current dir or parent folder
|
||||
cli.WithDefaultConfigPath,
|
||||
// .. and then, a project directory != PWD maybe has been set so let's load .env file
|
||||
cli.WithEnvFiles(o.EnvFiles...),
|
||||
cli.WithDotEnv,
|
||||
cli.WithConfigFileEnv,
|
||||
cli.WithDefaultConfigPath,
|
||||
// eventually COMPOSE_PROFILES should have been set
|
||||
cli.WithDefaultProfiles(o.Profiles...),
|
||||
cli.WithName(o.ProjectName))...)
|
||||
}
|
||||
@@ -281,7 +417,7 @@ func RunningAsStandalone() bool {
|
||||
}
|
||||
|
||||
// RootCommand returns the compose command with its child commands
|
||||
func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //nolint:gocyclo
|
||||
func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //nolint:gocyclo
|
||||
// filter out useless commandConn.CloseWrite warning message that can occur
|
||||
// when using a remote context that is unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
|
||||
// https://github.com/docker/cli/blob/e1f24d3c93df6752d3c27c8d61d18260f141310c/cli/connhelper/commandconn/commandconn.go#L203-L215
|
||||
@@ -292,6 +428,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
"commandConn.CloseRead:",
|
||||
))
|
||||
|
||||
experiments := experimental.NewState()
|
||||
opts := ProjectOptions{}
|
||||
var (
|
||||
ansi string
|
||||
@@ -303,7 +440,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
)
|
||||
c := &cobra.Command{
|
||||
Short: "Docker Compose",
|
||||
Long: "Define and run multi-container applications with Docker.",
|
||||
Long: "Define and run multi-container applications with Docker",
|
||||
Use: PluginName,
|
||||
TraverseChildren: true,
|
||||
// By default (no Run/RunE in parent c) for typos in subcommands, cobra displays the help of parent c but exit(0) !
|
||||
@@ -321,11 +458,9 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
}
|
||||
},
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
err := setEnvWithDotEnv(&opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx := cmd.Context()
|
||||
parent := cmd.Root()
|
||||
|
||||
if parent != nil {
|
||||
parentPrerun := parent.PersistentPreRunE
|
||||
if parentPrerun != nil {
|
||||
@@ -335,6 +470,15 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if verbose {
|
||||
logrus.SetLevel(logrus.TraceLevel)
|
||||
}
|
||||
|
||||
err := setEnvWithDotEnv(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if noAnsi {
|
||||
if ansi != "auto" {
|
||||
return errors.New(`cannot specify DEPRECATED "--no-ansi" and "--ansi". Please use only "--ansi"`)
|
||||
@@ -342,14 +486,9 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
ansi = "never"
|
||||
fmt.Fprint(os.Stderr, "option '--no-ansi' is DEPRECATED ! Please use '--ansi' instead.\n")
|
||||
}
|
||||
if verbose {
|
||||
logrus.SetLevel(logrus.TraceLevel)
|
||||
}
|
||||
|
||||
if v, ok := os.LookupEnv("COMPOSE_ANSI"); ok && !cmd.Flags().Changed("ansi") {
|
||||
ansi = v
|
||||
}
|
||||
|
||||
formatter.SetANSIMode(dockerCli, ansi)
|
||||
|
||||
if noColor, ok := os.LookupEnv("NO_COLOR"); ok && noColor != "" {
|
||||
@@ -367,6 +506,9 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
switch opts.Progress {
|
||||
case ui.ModeAuto:
|
||||
ui.Mode = ui.ModeAuto
|
||||
if ansi == "never" {
|
||||
ui.Mode = ui.ModePlain
|
||||
}
|
||||
case ui.ModeTTY:
|
||||
if ansi == "never" {
|
||||
return fmt.Errorf("can't use --progress tty while ANSI support is disabled")
|
||||
@@ -379,10 +521,14 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
ui.Mode = ui.ModePlain
|
||||
case ui.ModeQuiet, "none":
|
||||
ui.Mode = ui.ModeQuiet
|
||||
case ui.ModeJSON:
|
||||
ui.Mode = ui.ModeJSON
|
||||
logrus.SetFormatter(&logrus.JSONFormatter{})
|
||||
default:
|
||||
return fmt.Errorf("unsupported --progress value %q", opts.Progress)
|
||||
}
|
||||
|
||||
// (4) options validation / normalization
|
||||
if opts.WorkDir != "" {
|
||||
if opts.ProjectDir != "" {
|
||||
return errors.New(`cannot specify DEPRECATED "--workdir" and "--project-directory". Please use only "--project-directory" instead`)
|
||||
@@ -392,7 +538,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
}
|
||||
for i, file := range opts.EnvFiles {
|
||||
if !filepath.IsAbs(file) {
|
||||
file, err = filepath.Abs(file)
|
||||
file, err := filepath.Abs(file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -419,13 +565,38 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
parallel = i
|
||||
}
|
||||
if parallel > 0 {
|
||||
logrus.Debugf("Limiting max concurrency to %d jobs", parallel)
|
||||
backend.MaxConcurrency(parallel)
|
||||
}
|
||||
ctx, err := backend.DryRunMode(cmd.Context(), dryRun)
|
||||
|
||||
// dry run detection
|
||||
ctx, err = backend.DryRunMode(ctx, dryRun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.SetContext(ctx)
|
||||
|
||||
// (6) Desktop integration
|
||||
var desktopCli *desktop.Client
|
||||
if !dryRun {
|
||||
if desktopCli, err = desktop.NewFromDockerClient(ctx, dockerCli); desktopCli != nil {
|
||||
logrus.Debugf("Enabled Docker Desktop integration (experimental) @ %s", desktopCli.Endpoint())
|
||||
backend.SetDesktopClient(desktopCli)
|
||||
} else if err != nil {
|
||||
// not fatal, Compose will still work but behave as though
|
||||
// it's not running as part of Docker Desktop
|
||||
logrus.Debugf("failed to enable Docker Desktop integration: %v", err)
|
||||
} else {
|
||||
logrus.Trace("Docker Desktop integration not enabled")
|
||||
}
|
||||
}
|
||||
|
||||
// (7) experimental features
|
||||
if err := experiments.Load(ctx, desktopCli); err != nil {
|
||||
logrus.Debugf("Failed to query feature flags from Desktop: %v", err)
|
||||
}
|
||||
backend.SetExperiments(experiments)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -439,11 +610,14 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
psCommand(&opts, dockerCli, backend),
|
||||
listCommand(dockerCli, backend),
|
||||
logsCommand(&opts, dockerCli, backend),
|
||||
configCommand(&opts, dockerCli, backend),
|
||||
configCommand(&opts, dockerCli),
|
||||
killCommand(&opts, dockerCli, backend),
|
||||
runCommand(&opts, dockerCli, backend),
|
||||
removeCommand(&opts, dockerCli, backend),
|
||||
execCommand(&opts, dockerCli, backend),
|
||||
attachCommand(&opts, dockerCli, backend),
|
||||
exportCommand(&opts, dockerCli, backend),
|
||||
commitCommand(&opts, dockerCli, backend),
|
||||
pauseCommand(&opts, dockerCli, backend),
|
||||
unpauseCommand(&opts, dockerCli, backend),
|
||||
topCommand(&opts, dockerCli, backend),
|
||||
@@ -458,6 +632,7 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
copyCommand(&opts, dockerCli, backend),
|
||||
waitCommand(&opts, dockerCli, backend),
|
||||
scaleCommand(&opts, dockerCli, backend),
|
||||
statsCommand(&opts, dockerCli),
|
||||
watchCommand(&opts, dockerCli, backend),
|
||||
alphaCommand(&opts, dockerCli, backend),
|
||||
)
|
||||
@@ -497,38 +672,46 @@ func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //
|
||||
return c
|
||||
}
|
||||
|
||||
func setEnvWithDotEnv(prjOpts *ProjectOptions) error {
|
||||
if len(prjOpts.EnvFiles) == 0 {
|
||||
if envFiles := os.Getenv(ComposeEnvFiles); envFiles != "" {
|
||||
prjOpts.EnvFiles = strings.Split(envFiles, ",")
|
||||
}
|
||||
}
|
||||
options, err := prjOpts.toProjectOptions()
|
||||
func setEnvWithDotEnv(opts ProjectOptions) error {
|
||||
options, err := cli.NewProjectOptions(opts.ConfigPaths,
|
||||
cli.WithWorkingDirectory(opts.ProjectDir),
|
||||
cli.WithOsEnv,
|
||||
cli.WithEnvFiles(opts.EnvFiles...),
|
||||
cli.WithDotEnv,
|
||||
)
|
||||
if err != nil {
|
||||
return compose.WrapComposeError(err)
|
||||
return nil
|
||||
}
|
||||
workingDir, err := options.GetWorkingDir()
|
||||
envFromFile, err := dotenv.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), options.EnvFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
envFromFile, err := dotenv.GetEnvFromFile(composegoutils.GetAsEqualsMap(os.Environ()), workingDir, options.EnvFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
for k, v := range envFromFile {
|
||||
if _, ok := os.LookupEnv(k); !ok { // Precedence to OS Env
|
||||
if err := os.Setenv(k, v); err != nil {
|
||||
return err
|
||||
if _, ok := os.LookupEnv(k); !ok {
|
||||
if err = os.Setenv(k, v); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
var printerModes = []string{
|
||||
ui.ModeAuto,
|
||||
ui.ModeTTY,
|
||||
ui.ModePlain,
|
||||
ui.ModeJSON,
|
||||
ui.ModeQuiet,
|
||||
}
|
||||
|
||||
func SetUnchangedOption(name string, experimentalFlag bool) bool {
|
||||
var value bool
|
||||
// If the var is defined we use that value first
|
||||
if envVar, ok := os.LookupEnv(name); ok {
|
||||
value = utils.StringToBool(envVar)
|
||||
} else {
|
||||
// if not, we try to get it from experimental feature flag
|
||||
value = experimentalFlag
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -19,32 +19,32 @@ package compose
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestFilterServices(t *testing.T) {
|
||||
p := &types.Project{
|
||||
Services: types.Services{
|
||||
{
|
||||
"foo": {
|
||||
Name: "foo",
|
||||
Links: []string{"bar"},
|
||||
},
|
||||
{
|
||||
"bar": {
|
||||
Name: "bar",
|
||||
DependsOn: map[string]types.ServiceDependency{
|
||||
"zot": {},
|
||||
},
|
||||
},
|
||||
{
|
||||
"zot": {
|
||||
Name: "zot",
|
||||
},
|
||||
{
|
||||
"qix": {
|
||||
Name: "qix",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := p.ForServices([]string{"bar"})
|
||||
p, err := p.WithSelectedServices([]string{"bar"})
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, len(p.Services), 2)
|
||||
|
||||
@@ -19,15 +19,20 @@ package compose
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/template"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
@@ -48,21 +53,33 @@ type configOptions struct {
|
||||
images bool
|
||||
hash string
|
||||
noConsistency bool
|
||||
variables bool
|
||||
environment bool
|
||||
}
|
||||
|
||||
func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
|
||||
po = append(po,
|
||||
po = append(po, o.ToProjectOptions()...)
|
||||
project, _, err := o.ProjectOptions.ToProject(ctx, dockerCli, services, po...)
|
||||
return project, err
|
||||
}
|
||||
|
||||
func (o *configOptions) ToModel(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (map[string]any, error) {
|
||||
po = append(po, o.ToProjectOptions()...)
|
||||
return o.ProjectOptions.ToModel(ctx, dockerCli, services, po...)
|
||||
}
|
||||
|
||||
func (o *configOptions) ToProjectOptions() []cli.ProjectOptionsFn {
|
||||
return []cli.ProjectOptionsFn{
|
||||
cli.WithInterpolation(!o.noInterpolate),
|
||||
cli.WithResolvedPaths(!o.noResolvePath),
|
||||
cli.WithNormalization(!o.noNormalize),
|
||||
cli.WithConsistency(!o.noConsistency),
|
||||
cli.WithDefaultProfiles(o.Profiles...),
|
||||
cli.WithDiscardEnvFile,
|
||||
cli.WithContext(ctx))
|
||||
return o.ProjectOptions.ToProject(dockerCli, services, po...)
|
||||
}
|
||||
}
|
||||
|
||||
func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
opts := configOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
@@ -99,18 +116,24 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
if opts.images {
|
||||
return runConfigImages(ctx, dockerCli, opts, args)
|
||||
}
|
||||
if opts.variables {
|
||||
return runVariables(ctx, dockerCli, opts, args)
|
||||
}
|
||||
if opts.environment {
|
||||
return runEnvironment(ctx, dockerCli, opts, args)
|
||||
}
|
||||
|
||||
return runConfig(ctx, dockerCli, backend, opts, args)
|
||||
return runConfig(ctx, dockerCli, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything.")
|
||||
flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables.")
|
||||
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model.")
|
||||
flags.BoolVar(&opts.noResolvePath, "no-path-resolution", false, "Don't resolve file paths.")
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Only validate the configuration, don't print anything")
|
||||
flags.BoolVar(&opts.noInterpolate, "no-interpolate", false, "Don't interpolate environment variables")
|
||||
flags.BoolVar(&opts.noNormalize, "no-normalize", false, "Don't normalize compose model")
|
||||
flags.BoolVar(&opts.noResolvePath, "no-path-resolution", false, "Don't resolve file paths")
|
||||
flags.BoolVar(&opts.noConsistency, "no-consistency", false, "Don't check model consistency - warning: may produce invalid Compose output")
|
||||
|
||||
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
|
||||
@@ -118,25 +141,25 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
flags.BoolVar(&opts.profiles, "profiles", false, "Print the profile names, one per line.")
|
||||
flags.BoolVar(&opts.images, "images", false, "Print the image names, one per line.")
|
||||
flags.StringVar(&opts.hash, "hash", "", "Print the service config hash, one per line.")
|
||||
flags.BoolVar(&opts.variables, "variables", false, "Print model variables and default values.")
|
||||
flags.BoolVar(&opts.environment, "environment", false, "Print environment used for interpolation.")
|
||||
flags.StringVarP(&opts.Output, "output", "o", "", "Save to file (default to stdout)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service, opts configOptions, services []string) error {
|
||||
func runConfig(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) (err error) {
|
||||
var content []byte
|
||||
project, err := opts.ToProject(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err = backend.Config(ctx, project, api.ConfigOptions{
|
||||
Format: opts.Format,
|
||||
Output: opts.Output,
|
||||
ResolveImageDigests: opts.resolveImageDigests,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
if opts.noInterpolate {
|
||||
content, err = runConfigNoInterpolate(ctx, dockerCli, opts, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
content, err = runConfigInterpolate(ctx, dockerCli, opts, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.noInterpolate {
|
||||
@@ -154,15 +177,134 @@ func runConfig(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
return err
|
||||
}
|
||||
|
||||
func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) ([]byte, error) {
|
||||
project, err := opts.ToProject(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.resolveImageDigests {
|
||||
project, err = project.WithImagesResolved(compose.ImageDigestResolver(ctx, dockerCli.ConfigFile(), dockerCli.Client()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.noConsistency {
|
||||
err := project.CheckContainerNameUnicity()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var content []byte
|
||||
switch opts.Format {
|
||||
case "json":
|
||||
content, err = project.MarshalJSON()
|
||||
case "yaml":
|
||||
content, err = project.MarshalYAML()
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported format %q", opts.Format)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return content, nil
|
||||
}
|
||||
|
||||
func runConfigNoInterpolate(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) ([]byte, error) {
|
||||
// we can't use ToProject, so the model we render here is only partially resolved
|
||||
model, err := opts.ToModel(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if opts.resolveImageDigests {
|
||||
err = resolveImageDigests(ctx, dockerCli, model)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return formatModel(model, opts.Format)
|
||||
}
|
||||
|
||||
func resolveImageDigests(ctx context.Context, dockerCli command.Cli, model map[string]any) (err error) {
|
||||
// create a pseudo-project so we can rely on WithImagesResolved to resolve images
|
||||
p := &types.Project{
|
||||
Services: types.Services{},
|
||||
}
|
||||
services := model["services"].(map[string]any)
|
||||
for name, s := range services {
|
||||
service := s.(map[string]any)
|
||||
if image, ok := service["image"]; ok {
|
||||
p.Services[name] = types.ServiceConfig{
|
||||
Image: image.(string),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p, err = p.WithImagesResolved(compose.ImageDigestResolver(ctx, dockerCli.ConfigFile(), dockerCli.Client()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Collect image resolved with digest and update model accordingly
|
||||
for name, s := range services {
|
||||
service := s.(map[string]any)
|
||||
config := p.Services[name]
|
||||
if config.Image != "" {
|
||||
service["image"] = config.Image
|
||||
}
|
||||
services[name] = service
|
||||
}
|
||||
model["services"] = services
|
||||
return nil
|
||||
}
|
||||
|
||||
func formatModel(model map[string]any, format string) (content []byte, err error) {
|
||||
switch format {
|
||||
case "json":
|
||||
content, err = json.MarshalIndent(model, "", " ")
|
||||
case "yaml":
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
encoder := yaml.NewEncoder(buf)
|
||||
encoder.SetIndent(2)
|
||||
err = encoder.Encode(model)
|
||||
content = buf.Bytes()
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported format %q", format)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func runServices(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
|
||||
if opts.noInterpolate {
|
||||
// we can't use ToProject, so the model we render here is only partially resolved
|
||||
data, err := opts.ToModel(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := data["services"]; ok {
|
||||
for serviceName := range data["services"].(map[string]any) {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), serviceName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return project.WithServices(project.ServiceNames(), func(s types.ServiceConfig) error {
|
||||
fmt.Fprintln(dockerCli.Out(), s.Name)
|
||||
err = project.ForEachService(project.ServiceNames(), func(serviceName string, _ *types.ServiceConfig) error {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), serviceName)
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
|
||||
@@ -171,7 +313,7 @@ func runVolumes(ctx context.Context, dockerCli command.Cli, opts configOptions)
|
||||
return err
|
||||
}
|
||||
for n := range project.Volumes {
|
||||
fmt.Fprintln(dockerCli.Out(), n)
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -206,11 +348,10 @@ func runHash(ctx context.Context, dockerCli command.Cli, opts configOptions) err
|
||||
}
|
||||
|
||||
hash, err := compose.ServiceHash(s)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(dockerCli.Out(), "%s %s\n", s.Name, hash)
|
||||
_, _ = fmt.Fprintf(dockerCli.Out(), "%s %s\n", name, hash)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -232,7 +373,7 @@ func runProfiles(ctx context.Context, dockerCli command.Cli, opts configOptions,
|
||||
}
|
||||
sort.Strings(profiles)
|
||||
for _, p := range profiles {
|
||||
fmt.Fprintln(dockerCli.Out(), p)
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -242,8 +383,37 @@ func runConfigImages(ctx context.Context, dockerCli command.Cli, opts configOpti
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, s := range project.Services {
|
||||
fmt.Fprintln(dockerCli.Out(), api.GetImageNameOrDefault(s, project.Name))
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), api.GetImageNameOrDefault(s, project.Name))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runVariables(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
|
||||
opts.noInterpolate = true
|
||||
model, err := opts.ToModel(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
variables := template.ExtractVariables(model, template.DefaultPattern)
|
||||
|
||||
return formatter.Print(variables, "", dockerCli.Out(), func(w io.Writer) {
|
||||
for name, variable := range variables {
|
||||
_, _ = fmt.Fprintf(w, "%s\t%t\t%s\t%s\n", name, variable.Required, variable.DefaultValue, variable.PresenceValue)
|
||||
}
|
||||
}, "NAME", "REQUIRED", "DEFAULT VALUE", "ALTERNATE VALUE")
|
||||
}
|
||||
|
||||
func runEnvironment(ctx context.Context, dockerCli command.Cli, opts configOptions, services []string) error {
|
||||
project, err := opts.ToProject(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range project.Environment.Values() {
|
||||
fmt.Println(v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -65,10 +65,8 @@ func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
flags := copyCmd.Flags()
|
||||
flags.IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
|
||||
flags.BoolVar(&opts.all, "all", false, "copy to all the containers of the service.")
|
||||
flags.MarkHidden("all") //nolint:errcheck
|
||||
flags.MarkDeprecated("all", "by default all the containers of the service will get the source file/directory to be copied.") //nolint:errcheck
|
||||
flags.IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas")
|
||||
flags.BoolVar(&opts.all, "all", false, "Include containers created by the run command")
|
||||
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
|
||||
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
|
||||
|
||||
@@ -76,7 +74,7 @@ func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
func runCopy(ctx context.Context, dockerCli command.Cli, backend api.Service, opts copyOptions) error {
|
||||
name, err := opts.toProjectName(dockerCli)
|
||||
name, err := opts.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -46,6 +46,7 @@ type createOptions struct {
|
||||
timeout int
|
||||
quietPull bool
|
||||
scale []string
|
||||
AssumeYes bool
|
||||
}
|
||||
|
||||
func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
@@ -55,7 +56,7 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [OPTIONS] [SERVICE...]",
|
||||
Short: "Creates containers for a service.",
|
||||
Short: "Creates containers for a service",
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
opts.pullChanged = cmd.Flags().Changed("pull")
|
||||
if opts.Build && opts.noBuild {
|
||||
@@ -72,13 +73,15 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers.")
|
||||
flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's policy.")
|
||||
flags.BoolVar(&opts.Build, "build", false, "Build images before starting containers")
|
||||
flags.BoolVar(&opts.noBuild, "no-build", false, "Don't build an image, even if it's policy")
|
||||
flags.StringVar(&opts.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never"|"build")`)
|
||||
flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
|
||||
flags.BoolVar(&opts.quietPull, "quiet-pull", false, "Pull without printing progress information")
|
||||
flags.BoolVar(&opts.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed")
|
||||
flags.BoolVar(&opts.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
||||
flags.StringArrayVar(&opts.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
|
||||
flags.BoolVarP(&opts.AssumeYes, "y", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -105,7 +108,8 @@ func runCreate(ctx context.Context, _ command.Cli, backend api.Service, createOp
|
||||
RecreateDependencies: createOpts.dependenciesRecreateStrategy(),
|
||||
Inherit: !createOpts.noInherit,
|
||||
Timeout: createOpts.GetTimeout(),
|
||||
QuietPull: false,
|
||||
QuietPull: createOpts.quietPull,
|
||||
AssumeYes: createOpts.AssumeYes,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -116,6 +120,9 @@ func (opts createOptions) recreateStrategy() string {
|
||||
if opts.forceRecreate {
|
||||
return api.RecreateForce
|
||||
}
|
||||
if opts.noInherit {
|
||||
return api.RecreateForce
|
||||
}
|
||||
return api.RecreateDiverged
|
||||
}
|
||||
|
||||
@@ -159,22 +166,20 @@ func (opts createOptions) Apply(project *types.Project) error {
|
||||
project.Services[i] = service
|
||||
}
|
||||
}
|
||||
// opts.noBuild, however, means do not perform ANY builds
|
||||
if opts.noBuild {
|
||||
for i, service := range project.Services {
|
||||
service.Build = nil
|
||||
if service.Image == "" {
|
||||
service.Image = api.GetImageNameOrDefault(service, project.Name)
|
||||
}
|
||||
project.Services[i] = service
|
||||
}
|
||||
}
|
||||
|
||||
if err := applyPlatforms(project, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, scale := range opts.scale {
|
||||
err := applyScaleOpts(project, opts.scale)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyScaleOpts(project *types.Project, opts []string) error {
|
||||
for _, scale := range opts {
|
||||
split := strings.Split(scale, "=")
|
||||
if len(split) != 2 {
|
||||
return fmt.Errorf("invalid --scale option %q. Should be SERVICE=NUM", scale)
|
||||
@@ -184,7 +189,7 @@ func (opts createOptions) Apply(project *types.Project) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = setServiceScale(project, name, uint64(replicas))
|
||||
err = setServiceScale(project, name, replicas)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -193,7 +198,9 @@ func (opts createOptions) Apply(project *types.Project) error {
|
||||
}
|
||||
|
||||
func (opts createOptions) isPullPolicyValid() bool {
|
||||
pullPolicies := []string{types.PullPolicyAlways, types.PullPolicyNever, types.PullPolicyBuild,
|
||||
types.PullPolicyMissing, types.PullPolicyIfNotPresent}
|
||||
pullPolicies := []string{
|
||||
types.PullPolicyAlways, types.PullPolicyNever, types.PullPolicyBuild,
|
||||
types.PullPolicyMissing, types.PullPolicyIfNotPresent,
|
||||
}
|
||||
return slices.Contains(pullPolicies, opts.Pull)
|
||||
}
|
||||
|
||||
@@ -21,13 +21,13 @@ import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestRunCreate(t *testing.T) {
|
||||
@@ -86,7 +86,7 @@ func sampleProject() *types.Project {
|
||||
return &types.Project{
|
||||
Name: "test",
|
||||
Services: types.Services{
|
||||
{
|
||||
"svc": {
|
||||
Name: "svc",
|
||||
Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
|
||||
@@ -63,9 +63,9 @@ func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
flags := downCmd.Flags()
|
||||
removeOrphans := utils.StringToBool(os.Getenv(ComposeRemoveOrphans))
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file")
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds")
|
||||
flags.BoolVarP(&opts.volumes, "volumes", "v", false, `Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers.`)
|
||||
flags.BoolVarP(&opts.volumes, "volumes", "v", false, `Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers`)
|
||||
flags.StringVar(&opts.images, "rmi", "", `Remove images used by services. "local" remove only images that don't have a custom tag ("local"|"all")`)
|
||||
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
if name == "volume" {
|
||||
@@ -78,7 +78,7 @@ func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
func runDown(ctx context.Context, dockerCli command.Cli, backend api.Service, opts downOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "events [OPTIONS] [SERVICE...]",
|
||||
Short: "Receive real time events from containers.",
|
||||
Short: "Receive real time events from containers",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runEvents(ctx, dockerCli, backend, opts, args)
|
||||
}),
|
||||
@@ -52,7 +52,7 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
}
|
||||
|
||||
func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Service, opts eventsOpts, services []string) error {
|
||||
name, err := opts.toProjectName(dockerCli)
|
||||
name, err := opts.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -72,9 +72,9 @@ func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(dockerCli.Out(), string(marshal))
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), string(marshal))
|
||||
} else {
|
||||
fmt.Fprintln(dockerCli.Out(), event)
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), event)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -51,7 +51,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
runCmd := &cobra.Command{
|
||||
Use: "exec [OPTIONS] SERVICE COMMAND [ARGS...]",
|
||||
Short: "Execute a command in a running container.",
|
||||
Short: "Execute a command in a running container",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
opts.service = args[0]
|
||||
@@ -64,17 +64,17 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background.")
|
||||
runCmd.Flags().BoolVarP(&opts.detach, "detach", "d", false, "Detached mode: Run command in the background")
|
||||
runCmd.Flags().StringArrayVarP(&opts.environment, "env", "e", []string{}, "Set environment variables")
|
||||
runCmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
|
||||
runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process.")
|
||||
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user.")
|
||||
runCmd.Flags().IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas")
|
||||
runCmd.Flags().BoolVarP(&opts.privileged, "privileged", "", false, "Give extended privileges to the process")
|
||||
runCmd.Flags().StringVarP(&opts.user, "user", "u", "", "Run the command as this user")
|
||||
runCmd.Flags().BoolVarP(&opts.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY.")
|
||||
runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command.")
|
||||
runCmd.Flags().StringVarP(&opts.workingDir, "workdir", "w", "", "Path to workdir directory for this command")
|
||||
|
||||
runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
|
||||
runCmd.Flags().BoolVarP(&opts.interactive, "interactive", "i", true, "Keep STDIN open even if not attached")
|
||||
runCmd.Flags().MarkHidden("interactive") //nolint:errcheck
|
||||
runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY.")
|
||||
runCmd.Flags().BoolP("tty", "t", true, "Allocate a pseudo-TTY")
|
||||
runCmd.Flags().MarkHidden("tty") //nolint:errcheck
|
||||
|
||||
runCmd.Flags().SetInterspersed(false)
|
||||
@@ -82,7 +82,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, opts execOpts) error {
|
||||
projectName, err := opts.toProjectName(dockerCli)
|
||||
projectName, err := opts.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
74
cmd/compose/export.go
Normal file
74
cmd/compose/export.go
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type exportOptions struct {
|
||||
*ProjectOptions
|
||||
|
||||
service string
|
||||
output string
|
||||
index int
|
||||
}
|
||||
|
||||
func exportCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
options := exportOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "export [OPTIONS] SERVICE",
|
||||
Short: "Export a service container's filesystem as a tar archive",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
options.service = args[0]
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runExport(ctx, dockerCli, backend, options)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.IntVar(&options.index, "index", 0, "index of the container if service has multiple replicas.")
|
||||
flags.StringVarP(&options.output, "output", "o", "", "Write to a file, instead of STDOUT")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runExport(ctx context.Context, dockerCli command.Cli, backend api.Service, options exportOptions) error {
|
||||
projectName, err := options.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
exportOptions := api.ExportOptions{
|
||||
Service: options.service,
|
||||
Index: options.index,
|
||||
Output: options.output,
|
||||
}
|
||||
|
||||
return backend.Export(ctx, projectName, exportOptions)
|
||||
}
|
||||
82
cmd/compose/generate.go
Normal file
82
cmd/compose/generate.go
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright 2023 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type generateOptions struct {
|
||||
*ProjectOptions
|
||||
Format string
|
||||
}
|
||||
|
||||
func generateCommand(p *ProjectOptions, backend api.Service) *cobra.Command {
|
||||
opts := generateOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "generate [OPTIONS] [CONTAINERS...]",
|
||||
Short: "EXPERIMENTAL - Generate a Compose file from existing containers",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runGenerate(ctx, backend, opts, args)
|
||||
}),
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&opts.ProjectName, "name", "", "Project name to set in the Compose file")
|
||||
cmd.Flags().StringVar(&opts.ProjectDir, "project-dir", "", "Directory to use for the project")
|
||||
cmd.Flags().StringVar(&opts.Format, "format", "yaml", "Format the output. Values: [yaml | json]")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runGenerate(ctx context.Context, backend api.Service, opts generateOptions, containers []string) error {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "generate command is EXPERIMENTAL")
|
||||
if len(containers) == 0 {
|
||||
return fmt.Errorf("at least one container must be specified")
|
||||
}
|
||||
project, err := backend.Generate(ctx, api.GenerateOptions{
|
||||
Containers: containers,
|
||||
ProjectName: opts.ProjectName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var content []byte
|
||||
switch opts.Format {
|
||||
case "json":
|
||||
content, err = project.MarshalJSON()
|
||||
case "yaml":
|
||||
content, err = project.MarshalYAML()
|
||||
default:
|
||||
return fmt.Errorf("unsupported format %q", opts.Format)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(content))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -51,13 +51,13 @@ func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
imgCmd.Flags().StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json].")
|
||||
imgCmd.Flags().StringVar(&opts.Format, "format", "table", "Format the output. Values: [table | json]")
|
||||
imgCmd.Flags().BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||
return imgCmd
|
||||
}
|
||||
|
||||
func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service, opts imageOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName(dockerCli)
|
||||
projectName, err := opts.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
}
|
||||
}
|
||||
for _, img := range ids {
|
||||
fmt.Fprintln(dockerCli.Out(), img)
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), img)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "kill [OPTIONS] [SERVICE...]",
|
||||
Short: "Force stop service containers.",
|
||||
Short: "Force stop service containers",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runKill(ctx, dockerCli, backend, opts, args)
|
||||
}),
|
||||
@@ -48,14 +48,14 @@ func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
|
||||
flags := cmd.Flags()
|
||||
removeOrphans := utils.StringToBool(os.Getenv(ComposeRemoveOrphans))
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file.")
|
||||
flags.StringVarP(&opts.signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container.")
|
||||
flags.BoolVar(&opts.removeOrphans, "remove-orphans", removeOrphans, "Remove containers for services not defined in the Compose file")
|
||||
flags.StringVarP(&opts.signal, "signal", "s", "SIGKILL", "SIGNAL to send to the container")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runKill(ctx context.Context, dockerCli command.Cli, backend api.Service, opts killOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@ func listCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
Args: cobra.NoArgs,
|
||||
ValidArgsFunction: noCompletion(),
|
||||
}
|
||||
lsCmd.Flags().StringVar(&lsOpts.Format, "format", "table", "Format the output. Values: [table | json].")
|
||||
lsCmd.Flags().BoolVarP(&lsOpts.Quiet, "quiet", "q", false, "Only display IDs.")
|
||||
lsCmd.Flags().Var(&lsOpts.Filter, "filter", "Filter output based on conditions provided.")
|
||||
lsCmd.Flags().StringVar(&lsOpts.Format, "format", "table", "Format the output. Values: [table | json]")
|
||||
lsCmd.Flags().BoolVarP(&lsOpts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||
lsCmd.Flags().Var(&lsOpts.Filter, "filter", "Filter output based on conditions provided")
|
||||
lsCmd.Flags().BoolVarP(&lsOpts.All, "all", "a", false, "Show all stopped Compose projects")
|
||||
|
||||
return lsCmd
|
||||
@@ -86,7 +86,7 @@ func runList(ctx context.Context, dockerCli command.Cli, backend api.Service, ls
|
||||
|
||||
if lsOpts.Quiet {
|
||||
for _, s := range stackList {
|
||||
fmt.Fprintln(dockerCli.Out(), s.Name)
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), s.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -30,6 +31,7 @@ type logsOptions struct {
|
||||
*ProjectOptions
|
||||
composeOptions
|
||||
follow bool
|
||||
index int
|
||||
tail string
|
||||
since string
|
||||
until string
|
||||
@@ -48,29 +50,47 @@ func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runLogs(ctx, dockerCli, backend, opts, args)
|
||||
}),
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if opts.index > 0 && len(args) != 1 {
|
||||
return errors.New("--index requires one service to be selected")
|
||||
}
|
||||
return nil
|
||||
},
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
flags := logsCmd.Flags()
|
||||
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output.")
|
||||
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output")
|
||||
flags.IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
|
||||
flags.StringVar(&opts.since, "since", "", "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)")
|
||||
flags.StringVar(&opts.until, "until", "", "Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)")
|
||||
flags.BoolVar(&opts.noColor, "no-color", false, "Produce monochrome output.")
|
||||
flags.BoolVar(&opts.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.")
|
||||
flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps.")
|
||||
flags.StringVarP(&opts.tail, "tail", "n", "all", "Number of lines to show from the end of the logs for each container.")
|
||||
flags.BoolVar(&opts.noColor, "no-color", false, "Produce monochrome output")
|
||||
flags.BoolVar(&opts.noPrefix, "no-log-prefix", false, "Don't print prefix in logs")
|
||||
flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps")
|
||||
flags.StringVarP(&opts.tail, "tail", "n", "all", "Number of lines to show from the end of the logs for each container")
|
||||
return logsCmd
|
||||
}
|
||||
|
||||
func runLogs(ctx context.Context, dockerCli command.Cli, backend api.Service, opts logsOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// exclude services configured to ignore output (attach: false), until explicitly selected
|
||||
if project != nil && len(services) == 0 {
|
||||
for n, service := range project.Services {
|
||||
if service.Attach == nil || *service.Attach {
|
||||
services = append(services, n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), !opts.noColor, !opts.noPrefix, false)
|
||||
return backend.Logs(ctx, name, consumer, api.LogOptions{
|
||||
Project: project,
|
||||
Services: services,
|
||||
Follow: opts.follow,
|
||||
Index: opts.index,
|
||||
Tail: opts.tail,
|
||||
Since: opts.since,
|
||||
Until: opts.until,
|
||||
|
||||
@@ -19,16 +19,13 @@ package compose
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
defaultPlatform := project.Environment["DOCKER_DEFAULT_PLATFORM"]
|
||||
for i := range project.Services {
|
||||
// mutable reference so platform fields can be updated
|
||||
service := &project.Services[i]
|
||||
|
||||
for name, service := range project.Services {
|
||||
if service.Build == nil {
|
||||
continue
|
||||
}
|
||||
@@ -36,7 +33,7 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
// default platform only applies if the service doesn't specify
|
||||
if defaultPlatform != "" && service.Platform == "" {
|
||||
if len(service.Build.Platforms) > 0 && !utils.StringContains(service.Build.Platforms, defaultPlatform) {
|
||||
return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", service.Name, defaultPlatform)
|
||||
return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", name, defaultPlatform)
|
||||
}
|
||||
service.Platform = defaultPlatform
|
||||
}
|
||||
@@ -44,7 +41,7 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
if service.Platform != "" {
|
||||
if len(service.Build.Platforms) > 0 {
|
||||
if !utils.StringContains(service.Build.Platforms, service.Platform) {
|
||||
return fmt.Errorf("service %q build configuration does not support platform: %s", service.Name, service.Platform)
|
||||
return fmt.Errorf("service %q build configuration does not support platform: %s", name, service.Platform)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +68,7 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
// empty indicates that the builder gets to decide
|
||||
service.Build.Platforms = nil
|
||||
}
|
||||
project.Services[name] = service
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ package compose
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestApplyPlatforms_InferFromRuntime(t *testing.T) {
|
||||
makeProject := func() *types.Project {
|
||||
return &types.Project{
|
||||
Services: types.Services{
|
||||
{
|
||||
"test": {
|
||||
Name: "test",
|
||||
Image: "foo",
|
||||
Build: &types.BuildConfig{
|
||||
@@ -47,14 +47,14 @@ func TestApplyPlatforms_InferFromRuntime(t *testing.T) {
|
||||
t.Run("SinglePlatform", func(t *testing.T) {
|
||||
project := makeProject()
|
||||
require.NoError(t, applyPlatforms(project, true))
|
||||
require.EqualValues(t, []string{"alice/32"}, project.Services[0].Build.Platforms)
|
||||
require.EqualValues(t, []string{"alice/32"}, project.Services["test"].Build.Platforms)
|
||||
})
|
||||
|
||||
t.Run("MultiPlatform", func(t *testing.T) {
|
||||
project := makeProject()
|
||||
require.NoError(t, applyPlatforms(project, false))
|
||||
require.EqualValues(t, []string{"linux/amd64", "linux/arm64", "alice/32"},
|
||||
project.Services[0].Build.Platforms)
|
||||
project.Services["test"].Build.Platforms)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func TestApplyPlatforms_DockerDefaultPlatform(t *testing.T) {
|
||||
"DOCKER_DEFAULT_PLATFORM": "linux/amd64",
|
||||
},
|
||||
Services: types.Services{
|
||||
{
|
||||
"test": {
|
||||
Name: "test",
|
||||
Image: "foo",
|
||||
Build: &types.BuildConfig{
|
||||
@@ -83,14 +83,14 @@ func TestApplyPlatforms_DockerDefaultPlatform(t *testing.T) {
|
||||
t.Run("SinglePlatform", func(t *testing.T) {
|
||||
project := makeProject()
|
||||
require.NoError(t, applyPlatforms(project, true))
|
||||
require.EqualValues(t, []string{"linux/amd64"}, project.Services[0].Build.Platforms)
|
||||
require.EqualValues(t, []string{"linux/amd64"}, project.Services["test"].Build.Platforms)
|
||||
})
|
||||
|
||||
t.Run("MultiPlatform", func(t *testing.T) {
|
||||
project := makeProject()
|
||||
require.NoError(t, applyPlatforms(project, false))
|
||||
require.EqualValues(t, []string{"linux/amd64", "linux/arm64"},
|
||||
project.Services[0].Build.Platforms)
|
||||
project.Services["test"].Build.Platforms)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ func TestApplyPlatforms_UnsupportedPlatform(t *testing.T) {
|
||||
"DOCKER_DEFAULT_PLATFORM": "commodore/64",
|
||||
},
|
||||
Services: types.Services{
|
||||
{
|
||||
"test": {
|
||||
Name: "test",
|
||||
Image: "foo",
|
||||
Build: &types.BuildConfig{
|
||||
|
||||
@@ -45,7 +45,7 @@ func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
func runPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pauseOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
|
||||
}
|
||||
|
||||
func runUnPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts unpauseOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "port [OPTIONS] SERVICE PRIVATE_PORT",
|
||||
Short: "Print the public port for a port binding.",
|
||||
Short: "Print the public port for a port binding",
|
||||
Args: cobra.MinimumNArgs(2),
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
port, err := strconv.ParseUint(args[1], 10, 16)
|
||||
@@ -58,12 +58,12 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
cmd.Flags().StringVar(&opts.protocol, "protocol", "tcp", "tcp or udp")
|
||||
cmd.Flags().IntVar(&opts.index, "index", 0, "index of the container if service has multiple replicas")
|
||||
cmd.Flags().IntVar(&opts.index, "index", 0, "Index of the container if service has multiple replicas")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPort(ctx context.Context, dockerCli command.Cli, backend api.Service, opts portOptions, service string) error {
|
||||
projectName, err := opts.toProjectName(dockerCli)
|
||||
projectName, err := opts.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -75,6 +75,6 @@ func runPort(ctx context.Context, dockerCli command.Cli, backend api.Service, op
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(dockerCli.Out(), "%s:%d\n", ip, port)
|
||||
_, _ = fmt.Fprintf(dockerCli.Out(), "%s:%d\n", ip, port)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ type psOptions struct {
|
||||
Filter string
|
||||
Status []string
|
||||
noTrunc bool
|
||||
Orphans bool
|
||||
}
|
||||
|
||||
func (p *psOptions) parseFilter() error {
|
||||
@@ -80,33 +81,39 @@ func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
|
||||
}
|
||||
flags := psCmd.Flags()
|
||||
flags.StringVar(&opts.Format, "format", "table", cliflags.FormatHelp)
|
||||
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status).")
|
||||
flags.StringVar(&opts.Filter, "filter", "", "Filter services by a property (supported filters: status)")
|
||||
flags.StringArrayVar(&opts.Status, "status", []string{}, "Filter services by status. Values: [paused | restarting | removing | running | dead | created | exited]")
|
||||
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
|
||||
flags.BoolVar(&opts.Services, "services", false, "Display services")
|
||||
flags.BoolVar(&opts.Orphans, "orphans", true, "Include orphaned services (not declared by project)")
|
||||
flags.BoolVarP(&opts.All, "all", "a", false, "Show all stopped containers (including those created by the run command)")
|
||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Don't truncate output")
|
||||
return psCmd
|
||||
}
|
||||
|
||||
func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, opts psOptions) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if project != nil && len(services) > 0 {
|
||||
if project != nil {
|
||||
names := project.ServiceNames()
|
||||
for _, service := range services {
|
||||
if !utils.StringContains(names, service) {
|
||||
return fmt.Errorf("no such service: %s", service)
|
||||
if len(services) > 0 {
|
||||
for _, service := range services {
|
||||
if !utils.StringContains(names, service) {
|
||||
return fmt.Errorf("no such service: %s", service)
|
||||
}
|
||||
}
|
||||
} else if !opts.Orphans {
|
||||
// until user asks to list orphaned services, we only include those declared in project
|
||||
services = names
|
||||
}
|
||||
}
|
||||
|
||||
containers, err := backend.Ps(ctx, name, api.PsOptions{
|
||||
Project: project,
|
||||
All: opts.All,
|
||||
All: opts.All || len(opts.Status) != 0,
|
||||
Services: services,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -123,7 +130,7 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv
|
||||
|
||||
if opts.Quiet {
|
||||
for _, c := range containers {
|
||||
fmt.Fprintln(dockerCli.Out(), c.ID)
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), c.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -136,7 +143,7 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv
|
||||
services = append(services, s)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(dockerCli.Out(), strings.Join(services, "\n"))
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), strings.Join(services, "\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,9 @@ import (
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestPsTable(t *testing.T) {
|
||||
@@ -74,13 +75,13 @@ func TestPsTable(t *testing.T) {
|
||||
cli.EXPECT().Out().Return(stdout).AnyTimes()
|
||||
cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
|
||||
err = runPs(ctx, cli, backend, nil, opts)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = f.Seek(0, 0)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
output, err := os.ReadFile(out)
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Contains(t, string(output), "8080/tcp, 8443/tcp")
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
type publishOptions struct {
|
||||
*ProjectOptions
|
||||
resolveImageDigests bool
|
||||
ociVersion string
|
||||
}
|
||||
|
||||
func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
@@ -35,7 +36,7 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "publish [OPTIONS] [REPOSITORY]",
|
||||
Use: "publish [OPTIONS] REPOSITORY[:TAG]",
|
||||
Short: "Publish compose application",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPublish(ctx, dockerCli, backend, opts, args[0])
|
||||
@@ -43,17 +44,19 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests.")
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests")
|
||||
flags.StringVar(&opts.ociVersion, "oci-version", "", "OCI Image/Artifact specification version (automatically determined by default)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts publishOptions, repository string) error {
|
||||
project, err := opts.ToProject(dockerCli, nil)
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return backend.Publish(ctx, project, repository, api.PublishOptions{
|
||||
ResolveImageDigests: opts.resolveImageDigests,
|
||||
OCIVersion: api.OCIVersion(opts.ociVersion),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -60,23 +60,24 @@ func pullCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information.")
|
||||
cmd.Flags().BoolVar(&opts.includeDeps, "include-deps", false, "Also pull services declared as dependencies.")
|
||||
cmd.Flags().BoolVar(&opts.parallel, "parallel", true, "DEPRECATED pull multiple images in parallel.")
|
||||
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Pull without printing progress information")
|
||||
cmd.Flags().BoolVar(&opts.includeDeps, "include-deps", false, "Also pull services declared as dependencies")
|
||||
cmd.Flags().BoolVar(&opts.parallel, "parallel", true, "DEPRECATED pull multiple images in parallel")
|
||||
flags.MarkHidden("parallel") //nolint:errcheck
|
||||
cmd.Flags().BoolVar(&opts.parallel, "no-parallel", true, "DEPRECATED disable parallel pulling.")
|
||||
cmd.Flags().BoolVar(&opts.parallel, "no-parallel", true, "DEPRECATED disable parallel pulling")
|
||||
flags.MarkHidden("no-parallel") //nolint:errcheck
|
||||
cmd.Flags().BoolVar(&opts.ignorePullFailures, "ignore-pull-failures", false, "Pull what it can and ignores images with pull failures.")
|
||||
cmd.Flags().BoolVar(&opts.noBuildable, "ignore-buildable", false, "Ignore images that can be built.")
|
||||
cmd.Flags().StringVar(&opts.policy, "policy", "", `Apply pull policy ("missing"|"always").`)
|
||||
cmd.Flags().BoolVar(&opts.ignorePullFailures, "ignore-pull-failures", false, "Pull what it can and ignores images with pull failures")
|
||||
cmd.Flags().BoolVar(&opts.noBuildable, "ignore-buildable", false, "Ignore images that can be built")
|
||||
cmd.Flags().StringVar(&opts.policy, "policy", "", `Apply pull policy ("missing"|"always")`)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (opts pullOptions) apply(project *types.Project, services []string) error {
|
||||
func (opts pullOptions) apply(project *types.Project, services []string) (*types.Project, error) {
|
||||
if !opts.includeDeps {
|
||||
err := project.ForServices(services, types.IgnoreDependencies)
|
||||
var err error
|
||||
project, err = project.WithSelectedServices(services, types.IgnoreDependencies)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,16 +90,16 @@ func (opts pullOptions) apply(project *types.Project, services []string) error {
|
||||
project.Services[i] = service
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func runPull(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pullOptions, services []string) error {
|
||||
project, err := opts.ToProject(dockerCli, services)
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = opts.apply(project, services)
|
||||
project, err = opts.apply(project, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,39 +19,39 @@ package compose
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestApplyPullOptions(t *testing.T) {
|
||||
project := &types.Project{
|
||||
Services: types.Services{
|
||||
{
|
||||
"must-build": {
|
||||
Name: "must-build",
|
||||
// No image, local build only
|
||||
Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
},
|
||||
},
|
||||
{
|
||||
"has-build": {
|
||||
Name: "has-build",
|
||||
Image: "registry.example.com/myservice",
|
||||
Build: &types.BuildConfig{
|
||||
Context: ".",
|
||||
},
|
||||
},
|
||||
{
|
||||
"must-pull": {
|
||||
Name: "must-pull",
|
||||
Image: "registry.example.com/another-service",
|
||||
},
|
||||
},
|
||||
}
|
||||
err := pullOptions{
|
||||
project, err := pullOptions{
|
||||
policy: types.PullPolicyMissing,
|
||||
}.apply(project, nil)
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, project.Services[0].PullPolicy, "") // still default
|
||||
assert.Equal(t, project.Services[1].PullPolicy, types.PullPolicyMissing)
|
||||
assert.Equal(t, project.Services[2].PullPolicy, types.PullPolicyMissing)
|
||||
assert.Equal(t, project.Services["must-build"].PullPolicy, "") // still default
|
||||
assert.Equal(t, project.Services["has-build"].PullPolicy, types.PullPolicyMissing)
|
||||
assert.Equal(t, project.Services["must-pull"].PullPolicy, types.PullPolicyMissing)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
@@ -54,13 +54,13 @@ func pushCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
func runPush(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pushOptions, services []string) error {
|
||||
project, err := opts.ToProject(dockerCli, services)
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !opts.IncludeDeps {
|
||||
err := project.ForServices(services, types.IgnoreDependencies)
|
||||
project, err = project.WithSelectedServices(services, types.IgnoreDependencies)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ Any data which is not in a volume will be lost.`,
|
||||
}
|
||||
|
||||
func runRemove(ctx context.Context, dockerCli command.Cli, backend api.Service, opts removeOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -50,19 +50,19 @@ func restartCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic
|
||||
}
|
||||
flags := restartCmd.Flags()
|
||||
flags.IntVarP(&opts.timeout, "timeout", "t", 0, "Specify a shutdown timeout in seconds")
|
||||
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't restart dependent services.")
|
||||
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't restart dependent services")
|
||||
|
||||
return restartCmd
|
||||
}
|
||||
|
||||
func runRestart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts restartOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli)
|
||||
project, name, err := opts.projectOrName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if project != nil && len(services) > 0 {
|
||||
err := project.EnableServices(services...)
|
||||
project, err = project.WithServicesEnabled(services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -21,11 +21,12 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
xprogress "github.com/docker/buildx/util/progress"
|
||||
"github.com/compose-spec/compose-go/v2/format"
|
||||
xprogress "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
cgo "github.com/compose-spec/compose-go/cli"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
cgo "github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/mattn/go-shellwords"
|
||||
@@ -62,20 +63,22 @@ type runOptions struct {
|
||||
name string
|
||||
noDeps bool
|
||||
ignoreOrphans bool
|
||||
removeOrphans bool
|
||||
quietPull bool
|
||||
}
|
||||
|
||||
func (options runOptions) apply(project *types.Project) error {
|
||||
func (options runOptions) apply(project *types.Project) (*types.Project, error) {
|
||||
if options.noDeps {
|
||||
err := project.ForServices([]string{options.Service}, types.IgnoreDependencies)
|
||||
var err error
|
||||
project, err = project.WithSelectedServices([]string{options.Service}, types.IgnoreDependencies)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
target, err := project.GetService(options.Service)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
target.Tty = !options.noTty
|
||||
@@ -83,31 +86,34 @@ func (options runOptions) apply(project *types.Project) error {
|
||||
|
||||
// --service-ports and --publish are incompatible
|
||||
if !options.servicePorts {
|
||||
if len(target.Ports) > 0 {
|
||||
logrus.Debug("Running service without ports exposed as --service-ports=false")
|
||||
}
|
||||
target.Ports = []types.ServicePortConfig{}
|
||||
for _, p := range options.publish {
|
||||
config, err := types.ParsePortConfig(p)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
target.Ports = append(target.Ports, config...)
|
||||
}
|
||||
}
|
||||
|
||||
for _, v := range options.volumes {
|
||||
volume, err := loader.ParseVolume(v)
|
||||
volume, err := format.ParseVolume(v)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
target.Volumes = append(target.Volumes, volume)
|
||||
}
|
||||
|
||||
for i, s := range project.Services {
|
||||
if s.Name == options.Service {
|
||||
project.Services[i] = target
|
||||
for name := range project.Services {
|
||||
if name == options.Service {
|
||||
project.Services[name] = target
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
@@ -124,7 +130,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "run [OPTIONS] SERVICE [COMMAND] [ARGS...]",
|
||||
Short: "Run a one-off command on a service.",
|
||||
Short: "Run a one-off command on a service",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
options.Service = args[0]
|
||||
@@ -148,16 +154,17 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
options.noTty = !options.tty
|
||||
}
|
||||
}
|
||||
createOpts.pullChanged = cmd.Flags().Changed("pull")
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
project, err := p.ToProject(dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
|
||||
project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if createOpts.quietPull {
|
||||
buildOpts.Progress = xprogress.PrinterModeQuiet
|
||||
buildOpts.Progress = string(xprogress.QuietMode)
|
||||
}
|
||||
|
||||
options.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans])
|
||||
@@ -170,24 +177,25 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
flags.StringArrayVarP(&options.environment, "env", "e", []string{}, "Set environment variables")
|
||||
flags.StringArrayVarP(&options.labels, "label", "l", []string{}, "Add or override a label")
|
||||
flags.BoolVar(&options.Remove, "rm", false, "Automatically remove the container when it exits")
|
||||
flags.BoolVarP(&options.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected).")
|
||||
flags.BoolVarP(&options.noTty, "no-TTY", "T", !dockerCli.Out().IsTerminal(), "Disable pseudo-TTY allocation (default: auto-detected)")
|
||||
flags.StringVar(&options.name, "name", "", "Assign a name to the container")
|
||||
flags.StringVarP(&options.user, "user", "u", "", "Run as specified username or uid")
|
||||
flags.StringVarP(&options.workdir, "workdir", "w", "", "Working directory inside the container")
|
||||
flags.StringVar(&options.entrypoint, "entrypoint", "", "Override the entrypoint of the image")
|
||||
flags.Var(&options.capAdd, "cap-add", "Add Linux capabilities")
|
||||
flags.Var(&options.capDrop, "cap-drop", "Drop Linux capabilities")
|
||||
flags.BoolVar(&options.noDeps, "no-deps", false, "Don't start linked services.")
|
||||
flags.StringArrayVarP(&options.volumes, "volume", "v", []string{}, "Bind mount a volume.")
|
||||
flags.StringArrayVarP(&options.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host.")
|
||||
flags.BoolVar(&options.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to.")
|
||||
flags.BoolVar(&options.servicePorts, "service-ports", false, "Run command with the service's ports enabled and mapped to the host.")
|
||||
flags.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||
flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container.")
|
||||
flags.BoolVar(&createOpts.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
|
||||
flags.BoolVar(&options.noDeps, "no-deps", false, "Don't start linked services")
|
||||
flags.StringArrayVarP(&options.volumes, "volume", "v", []string{}, "Bind mount a volume")
|
||||
flags.StringArrayVarP(&options.publish, "publish", "p", []string{}, "Publish a container's port(s) to the host")
|
||||
flags.BoolVar(&options.useAliases, "use-aliases", false, "Use the service's network useAliases in the network(s) the container connects to")
|
||||
flags.BoolVarP(&options.servicePorts, "service-ports", "P", false, "Run command with all service's ports enabled and mapped to the host")
|
||||
flags.StringVar(&createOpts.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`)
|
||||
flags.BoolVar(&options.quietPull, "quiet-pull", false, "Pull without printing progress information")
|
||||
flags.BoolVar(&createOpts.Build, "build", false, "Build image before starting container")
|
||||
flags.BoolVar(&options.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
||||
|
||||
cmd.Flags().BoolVarP(&options.interactive, "interactive", "i", true, "Keep STDIN open even if not attached.")
|
||||
cmd.Flags().BoolVarP(&options.tty, "tty", "t", true, "Allocate a pseudo-TTY.")
|
||||
cmd.Flags().BoolVarP(&options.interactive, "interactive", "i", true, "Keep STDIN open even if not attached")
|
||||
cmd.Flags().BoolVarP(&options.tty, "tty", "t", true, "Allocate a pseudo-TTY")
|
||||
cmd.Flags().MarkHidden("tty") //nolint:errcheck
|
||||
|
||||
flags.SetNormalizeFunc(normalizeRunFlags)
|
||||
@@ -206,7 +214,7 @@ func normalizeRunFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
}
|
||||
|
||||
func runRun(ctx context.Context, backend api.Service, project *types.Project, options runOptions, createOpts createOptions, buildOpts buildOptions, dockerCli command.Cli) error {
|
||||
err := options.apply(project)
|
||||
project, err := options.apply(project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -226,7 +234,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
}
|
||||
buildForDeps = &bo
|
||||
}
|
||||
return startDependencies(ctx, backend, *project, buildForDeps, options.Service, options.ignoreOrphans)
|
||||
return startDependencies(ctx, backend, *project, buildForDeps, options)
|
||||
}, dockerCli.Err())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -275,10 +283,10 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
QuietPull: options.quietPull,
|
||||
}
|
||||
|
||||
for i, service := range project.Services {
|
||||
if service.Name == options.Service {
|
||||
for name, service := range project.Services {
|
||||
if name == options.Service {
|
||||
service.StdinOpen = options.interactive
|
||||
project.Services[i] = service
|
||||
project.Services[name] = service
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,22 +301,24 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
return err
|
||||
}
|
||||
|
||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, requestedServiceName string, ignoreOrphans bool) error {
|
||||
func startDependencies(ctx context.Context, backend api.Service, project types.Project, buildOpts *api.BuildOptions, options runOptions) error {
|
||||
dependencies := types.Services{}
|
||||
var requestedService types.ServiceConfig
|
||||
for _, service := range project.Services {
|
||||
if service.Name != requestedServiceName {
|
||||
dependencies = append(dependencies, service)
|
||||
for name, service := range project.Services {
|
||||
if name != options.Service {
|
||||
dependencies[name] = service
|
||||
} else {
|
||||
requestedService = service
|
||||
}
|
||||
}
|
||||
|
||||
project.Services = dependencies
|
||||
project.DisabledServices = append(project.DisabledServices, requestedService)
|
||||
project.DisabledServices[options.Service] = requestedService
|
||||
err := backend.Create(ctx, &project, api.CreateOptions{
|
||||
Build: buildOpts,
|
||||
IgnoreOrphans: ignoreOrphans,
|
||||
IgnoreOrphans: options.ignoreOrphans,
|
||||
RemoveOrphans: options.removeOrphans,
|
||||
QuietPull: options.quietPull,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -24,7 +24,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
@@ -54,37 +54,31 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
flags := scaleCmd.Flags()
|
||||
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services.")
|
||||
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services")
|
||||
|
||||
return scaleCmd
|
||||
}
|
||||
|
||||
func runScale(ctx context.Context, dockerCli command.Cli, backend api.Service, opts scaleOptions, serviceReplicaTuples map[string]int) error {
|
||||
services := maps.Keys(serviceReplicaTuples)
|
||||
project, err := opts.ToProject(dockerCli, services)
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.noDeps {
|
||||
if err := project.ForServices(services, types.IgnoreDependencies); err != nil {
|
||||
if project, err = project.WithSelectedServices(services, types.IgnoreDependencies); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for key, value := range serviceReplicaTuples {
|
||||
for i, service := range project.Services {
|
||||
if service.Name != key {
|
||||
continue
|
||||
}
|
||||
if service.Deploy == nil {
|
||||
service.Deploy = &types.DeployConfig{}
|
||||
}
|
||||
scale := uint64(value)
|
||||
service.Deploy.Replicas = &scale
|
||||
project.Services[i] = service
|
||||
break
|
||||
service, err := project.GetService(key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
service.SetScale(value)
|
||||
project.Services[key] = service
|
||||
}
|
||||
|
||||
return backend.Scale(ctx, project, api.ScaleOptions{Services: services})
|
||||
@@ -98,7 +92,6 @@ func parseServicesReplicasArgs(args []string) (map[string]int, error) {
|
||||
return nil, fmt.Errorf("invalid scale specifier: %s", arg)
|
||||
}
|
||||
intValue, err := strconv.Atoi(val)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid scale specifier: can't parse replica value as int: %v", arg)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func startCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
func runStart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts startOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
84
cmd/compose/stats.go
Normal file
84
cmd/compose/stats.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2020 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
|
||||
type statsOptions struct {
|
||||
ProjectOptions *ProjectOptions
|
||||
all bool
|
||||
format string
|
||||
noStream bool
|
||||
noTrunc bool
|
||||
}
|
||||
|
||||
func statsCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
opts := statsOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Use: "stats [OPTIONS] [SERVICE]",
|
||||
Short: "Display a live stream of container(s) resource usage statistics",
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runStats(ctx, dockerCli, opts, args)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
|
||||
flags.StringVar(&opts.format, "format", "", `Format output using a custom template:
|
||||
'table': Print output in table format with column headers (default)
|
||||
'table TEMPLATE': Print output in table format using the given Go template
|
||||
'json': Print in JSON format
|
||||
'TEMPLATE': Print output using the given Go template.
|
||||
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates`)
|
||||
flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result")
|
||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runStats(ctx context.Context, dockerCli command.Cli, opts statsOptions, service []string) error {
|
||||
name, err := opts.ProjectOptions.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filter := []filters.KeyValuePair{
|
||||
filters.Arg("label", fmt.Sprintf("%s=%s", api.ProjectLabel, name)),
|
||||
}
|
||||
if len(service) > 0 {
|
||||
filter = append(filter, filters.Arg("label", fmt.Sprintf("%s=%s", api.ServiceLabel, service[0])))
|
||||
}
|
||||
args := filters.NewArgs(filter...)
|
||||
return container.RunStats(ctx, dockerCli, &container.StatsOptions{
|
||||
All: opts.all,
|
||||
NoStream: opts.noStream,
|
||||
NoTrunc: opts.noTrunc,
|
||||
Format: opts.format,
|
||||
Filters: &args,
|
||||
})
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func stopCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
func runStop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts stopOptions, services []string) error {
|
||||
project, name, err := opts.projectOrName(dockerCli, services...)
|
||||
project, name, err := opts.projectOrName(ctx, dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
}
|
||||
|
||||
func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts topOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName(dockerCli)
|
||||
projectName, err := opts.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -64,7 +64,7 @@ func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opt
|
||||
})
|
||||
|
||||
for _, container := range containers {
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", container.Name)
|
||||
_, _ = fmt.Fprintf(dockerCli.Out(), "%s\n", container.Name)
|
||||
err := psPrinter(dockerCli.Out(), func(w io.Writer) {
|
||||
for _, proc := range container.Processes {
|
||||
info := []interface{}{}
|
||||
@@ -74,7 +74,7 @@ func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opt
|
||||
_, _ = fmt.Fprintf(w, strings.Repeat("%s\t", len(info))+"\n", info...)
|
||||
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
_, _ = fmt.Fprintln(w)
|
||||
},
|
||||
container.Titles...)
|
||||
if err != nil {
|
||||
|
||||
@@ -20,17 +20,18 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
xprogress "github.com/docker/buildx/util/progress"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
xprogress "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
@@ -41,37 +42,69 @@ type composeOptions struct {
|
||||
|
||||
type upOptions struct {
|
||||
*composeOptions
|
||||
Detach bool
|
||||
noStart bool
|
||||
noDeps bool
|
||||
cascadeStop bool
|
||||
exitCodeFrom string
|
||||
noColor bool
|
||||
noPrefix bool
|
||||
attachDependencies bool
|
||||
attach []string
|
||||
noAttach []string
|
||||
timestamp bool
|
||||
wait bool
|
||||
waitTimeout int
|
||||
Detach bool
|
||||
noStart bool
|
||||
noDeps bool
|
||||
cascadeStop bool
|
||||
cascadeFail bool
|
||||
exitCodeFrom string
|
||||
noColor bool
|
||||
noPrefix bool
|
||||
attachDependencies bool
|
||||
attach []string
|
||||
noAttach []string
|
||||
timestamp bool
|
||||
wait bool
|
||||
waitTimeout int
|
||||
watch bool
|
||||
navigationMenu bool
|
||||
navigationMenuChanged bool
|
||||
}
|
||||
|
||||
func (opts upOptions) apply(project *types.Project, services []string) error {
|
||||
func (opts upOptions) apply(project *types.Project, services []string) (*types.Project, error) {
|
||||
if opts.noDeps {
|
||||
err := project.ForServices(services, types.IgnoreDependencies)
|
||||
var err error
|
||||
project, err = project.WithSelectedServices(services, types.IgnoreDependencies)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.exitCodeFrom != "" {
|
||||
_, err := project.GetService(opts.exitCodeFrom)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (opts *upOptions) validateNavigationMenu(dockerCli command.Cli) {
|
||||
if !dockerCli.Out().IsTerminal() {
|
||||
opts.navigationMenu = false
|
||||
return
|
||||
}
|
||||
// If --menu flag was not set
|
||||
if !opts.navigationMenuChanged {
|
||||
if envVar, ok := os.LookupEnv(ComposeMenu); ok {
|
||||
opts.navigationMenu = utils.StringToBool(envVar)
|
||||
return
|
||||
}
|
||||
// ...and COMPOSE_MENU env var is not defined we want the default value to be true
|
||||
opts.navigationMenu = true
|
||||
}
|
||||
}
|
||||
|
||||
func (opts upOptions) OnExit() api.Cascade {
|
||||
switch {
|
||||
case opts.cascadeStop:
|
||||
return api.CascadeStop
|
||||
case opts.cascadeFail:
|
||||
return api.CascadeFail
|
||||
default:
|
||||
return api.CascadeIgnore
|
||||
}
|
||||
}
|
||||
|
||||
func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
@@ -84,6 +117,10 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
|
||||
PreRunE: AdaptCmd(func(ctx context.Context, cmd *cobra.Command, args []string) error {
|
||||
create.pullChanged = cmd.Flags().Changed("pull")
|
||||
create.timeChanged = cmd.Flags().Changed("timeout")
|
||||
up.navigationMenuChanged = cmd.Flags().Changed("menu")
|
||||
if !cmd.Flags().Changed("remove-orphans") {
|
||||
create.removeOrphans = utils.StringToBool(os.Getenv(ComposeRemoveOrphans))
|
||||
}
|
||||
return validateFlags(&up, &create)
|
||||
}),
|
||||
RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
|
||||
@@ -94,43 +131,58 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c
|
||||
if len(up.attach) != 0 && up.attachDependencies {
|
||||
return errors.New("cannot combine --attach and --attach-dependencies")
|
||||
}
|
||||
|
||||
up.validateNavigationMenu(dockerCli)
|
||||
|
||||
if !p.All && len(project.Services) == 0 {
|
||||
return fmt.Errorf("no service selected")
|
||||
}
|
||||
|
||||
return runUp(ctx, dockerCli, backend, create, up, build, project, services)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
flags := upCmd.Flags()
|
||||
flags.BoolVarP(&up.Detach, "detach", "d", false, "Detached mode: Run containers in the background")
|
||||
flags.BoolVar(&create.Build, "build", false, "Build images before starting containers.")
|
||||
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy.")
|
||||
flags.BoolVar(&create.Build, "build", false, "Build images before starting containers")
|
||||
flags.BoolVarP(&create.AssumeYes, "y", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
|
||||
flags.BoolVar(&create.noBuild, "no-build", false, "Don't build an image, even if it's policy")
|
||||
flags.StringVar(&create.Pull, "pull", "policy", `Pull image before running ("always"|"missing"|"never")`)
|
||||
flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file.")
|
||||
flags.BoolVar(&create.removeOrphans, "remove-orphans", false, "Remove containers for services not defined in the Compose file")
|
||||
flags.StringArrayVar(&create.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
|
||||
flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output.")
|
||||
flags.BoolVar(&up.noPrefix, "no-log-prefix", false, "Don't print prefix in logs.")
|
||||
flags.BoolVar(&create.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed.")
|
||||
flags.BoolVar(&up.noColor, "no-color", false, "Produce monochrome output")
|
||||
flags.BoolVar(&up.noPrefix, "no-log-prefix", false, "Don't print prefix in logs")
|
||||
flags.BoolVar(&create.forceRecreate, "force-recreate", false, "Recreate containers even if their configuration and image haven't changed")
|
||||
flags.BoolVar(&create.noRecreate, "no-recreate", false, "If containers already exist, don't recreate them. Incompatible with --force-recreate.")
|
||||
flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them.")
|
||||
flags.BoolVar(&up.noStart, "no-start", false, "Don't start the services after creating them")
|
||||
flags.BoolVar(&up.cascadeStop, "abort-on-container-exit", false, "Stops all containers if any container was stopped. Incompatible with -d")
|
||||
flags.BoolVar(&up.cascadeFail, "abort-on-container-failure", false, "Stops all containers if any container exited with failure. Incompatible with -d")
|
||||
flags.StringVar(&up.exitCodeFrom, "exit-code-from", "", "Return the exit code of the selected service container. Implies --abort-on-container-exit")
|
||||
flags.IntVarP(&create.timeout, "timeout", "t", 0, "Use this timeout in seconds for container shutdown when attached or when containers are already running.")
|
||||
flags.BoolVar(&up.timestamp, "timestamps", false, "Show timestamps.")
|
||||
flags.BoolVar(&up.noDeps, "no-deps", false, "Don't start linked services.")
|
||||
flags.IntVarP(&create.timeout, "timeout", "t", 0, "Use this timeout in seconds for container shutdown when attached or when containers are already running")
|
||||
flags.BoolVar(&up.timestamp, "timestamps", false, "Show timestamps")
|
||||
flags.BoolVar(&up.noDeps, "no-deps", false, "Don't start linked services")
|
||||
flags.BoolVar(&create.recreateDeps, "always-recreate-deps", false, "Recreate dependent containers. Incompatible with --no-recreate.")
|
||||
flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers.")
|
||||
flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information.")
|
||||
flags.BoolVarP(&create.noInherit, "renew-anon-volumes", "V", false, "Recreate anonymous volumes instead of retrieving data from the previous containers")
|
||||
flags.BoolVar(&create.quietPull, "quiet-pull", false, "Pull without printing progress information")
|
||||
flags.StringArrayVar(&up.attach, "attach", []string{}, "Restrict attaching to the specified services. Incompatible with --attach-dependencies.")
|
||||
flags.StringArrayVar(&up.noAttach, "no-attach", []string{}, "Do not attach (stream logs) to the specified services.")
|
||||
flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Automatically attach to log output of dependent services.")
|
||||
flags.StringArrayVar(&up.noAttach, "no-attach", []string{}, "Do not attach (stream logs) to the specified services")
|
||||
flags.BoolVar(&up.attachDependencies, "attach-dependencies", false, "Automatically attach to log output of dependent services")
|
||||
flags.BoolVar(&up.wait, "wait", false, "Wait for services to be running|healthy. Implies detached mode.")
|
||||
flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration to wait for the project to be running|healthy.")
|
||||
flags.IntVar(&up.waitTimeout, "wait-timeout", 0, "Maximum duration in seconds to wait for the project to be running|healthy")
|
||||
flags.BoolVarP(&up.watch, "watch", "w", false, "Watch source code and rebuild/refresh containers when files are updated.")
|
||||
flags.BoolVar(&up.navigationMenu, "menu", false, "Enable interactive shortcuts when running attached. Incompatible with --detach. Can also be enable/disable by setting COMPOSE_MENU environment var.")
|
||||
|
||||
return upCmd
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func validateFlags(up *upOptions, create *createOptions) error {
|
||||
if up.exitCodeFrom != "" {
|
||||
if up.exitCodeFrom != "" && !up.cascadeFail {
|
||||
up.cascadeStop = true
|
||||
}
|
||||
if up.cascadeStop && up.cascadeFail {
|
||||
return fmt.Errorf("--abort-on-container-failure cannot be combined with --abort-on-container-exit")
|
||||
}
|
||||
if up.wait {
|
||||
if up.attachDependencies || up.cascadeStop || len(up.attach) > 0 {
|
||||
return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
|
||||
@@ -140,8 +192,11 @@ func validateFlags(up *upOptions, create *createOptions) error {
|
||||
if create.Build && create.noBuild {
|
||||
return fmt.Errorf("--build and --no-build are incompatible")
|
||||
}
|
||||
if up.Detach && (up.attachDependencies || up.cascadeStop || len(up.attach) > 0) {
|
||||
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --attach or --attach-dependencies")
|
||||
if up.Detach && (up.attachDependencies || up.cascadeStop || up.cascadeFail || len(up.attach) > 0 || up.watch) {
|
||||
return fmt.Errorf("--detach cannot be combined with --abort-on-container-exit, --abort-on-container-failure, --attach, --attach-dependencies or --watch")
|
||||
}
|
||||
if create.noInherit && create.noRecreate {
|
||||
return fmt.Errorf("--no-recreate and --renew-anon-volumes are incompatible")
|
||||
}
|
||||
if create.forceRecreate && create.noRecreate {
|
||||
return fmt.Errorf("--force-recreate and --no-recreate are incompatible")
|
||||
@@ -149,6 +204,9 @@ func validateFlags(up *upOptions, create *createOptions) error {
|
||||
if create.recreateDeps && create.noRecreate {
|
||||
return fmt.Errorf("--always-recreate-deps and --no-recreate are incompatible")
|
||||
}
|
||||
if create.noBuild && up.watch {
|
||||
return fmt.Errorf("--no-build and --watch are incompatible")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -162,26 +220,20 @@ func runUp(
|
||||
project *types.Project,
|
||||
services []string,
|
||||
) error {
|
||||
if len(project.Services) == 0 {
|
||||
return fmt.Errorf("no service selected")
|
||||
}
|
||||
|
||||
err := createOptions.Apply(project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = upOptions.apply(project, services)
|
||||
project, err = upOptions.apply(project, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var build *api.BuildOptions
|
||||
// this check is technically redundant as createOptions::apply()
|
||||
// already removed all the build sections
|
||||
if !createOptions.noBuild {
|
||||
if createOptions.quietPull {
|
||||
buildOptions.Progress = xprogress.PrinterModeQuiet
|
||||
buildOptions.Progress = string(xprogress.QuietMode)
|
||||
}
|
||||
// BuildOptions here is nested inside CreateOptions, so
|
||||
// no service list is passed, it will implicitly pick all
|
||||
@@ -204,6 +256,7 @@ func runUp(
|
||||
Inherit: !createOptions.noInherit,
|
||||
Timeout: createOptions.GetTimeout(),
|
||||
QuietPull: createOptions.quietPull,
|
||||
AssumeYes: createOptions.AssumeYes,
|
||||
}
|
||||
|
||||
if upOptions.noStart {
|
||||
@@ -231,9 +284,9 @@ func runUp(
|
||||
if upOptions.attachDependencies {
|
||||
dependencyOpt = types.IncludeDependencies
|
||||
}
|
||||
if err := project.WithServices(services, func(s types.ServiceConfig) error {
|
||||
if err := project.ForEachService(services, func(serviceName string, s *types.ServiceConfig) error {
|
||||
if s.Attach == nil || *s.Attach {
|
||||
attachSet.Add(s.Name)
|
||||
attachSet.Add(serviceName)
|
||||
}
|
||||
return nil
|
||||
}, dependencyOpt); err != nil {
|
||||
@@ -249,34 +302,26 @@ func runUp(
|
||||
return backend.Up(ctx, project, api.UpOptions{
|
||||
Create: create,
|
||||
Start: api.StartOptions{
|
||||
Project: project,
|
||||
Attach: consumer,
|
||||
AttachTo: attach,
|
||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||
CascadeStop: upOptions.cascadeStop,
|
||||
Wait: upOptions.wait,
|
||||
WaitTimeout: timeout,
|
||||
Services: services,
|
||||
Project: project,
|
||||
Attach: consumer,
|
||||
AttachTo: attach,
|
||||
ExitCodeFrom: upOptions.exitCodeFrom,
|
||||
OnExit: upOptions.OnExit(),
|
||||
Wait: upOptions.wait,
|
||||
WaitTimeout: timeout,
|
||||
Watch: upOptions.watch,
|
||||
Services: services,
|
||||
NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func setServiceScale(project *types.Project, name string, replicas uint64) error {
|
||||
for i, s := range project.Services {
|
||||
if s.Name != name {
|
||||
continue
|
||||
}
|
||||
|
||||
service, err := project.GetService(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if service.Deploy == nil {
|
||||
service.Deploy = &types.DeployConfig{}
|
||||
}
|
||||
service.Deploy.Replicas = &replicas
|
||||
project.Services[i] = service
|
||||
return nil
|
||||
func setServiceScale(project *types.Project, name string, replicas int) error {
|
||||
service, err := project.GetService(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("unknown service %q", name)
|
||||
service.SetScale(replicas)
|
||||
project.Services[name] = service
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,25 +19,32 @@ package compose
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestApplyScaleOpt(t *testing.T) {
|
||||
p := types.Project{
|
||||
Services: types.Services{
|
||||
{
|
||||
"foo": {
|
||||
Name: "foo",
|
||||
},
|
||||
{
|
||||
"bar": {
|
||||
Name: "bar",
|
||||
Deploy: &types.DeployConfig{
|
||||
Mode: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
opt := createOptions{scale: []string{"foo=2"}}
|
||||
err := opt.Apply(&p)
|
||||
err := applyScaleOpts(&p, []string{"foo=2", "bar=3"})
|
||||
assert.NilError(t, err)
|
||||
foo, err := p.GetService("foo")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, *foo.Deploy.Replicas, uint64(2))
|
||||
assert.Equal(t, *foo.Scale, 2)
|
||||
|
||||
bar, err := p.GetService("bar")
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, *bar.Scale, 3)
|
||||
assert.Equal(t, *bar.Deploy.Replicas, 3)
|
||||
}
|
||||
|
||||
@@ -52,19 +52,19 @@ func versionCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// define flags for backward compatibility with com.docker.cli
|
||||
flags := cmd.Flags()
|
||||
flags.StringVarP(&opts.format, "format", "f", "", "Format the output. Values: [pretty | json]. (Default: pretty)")
|
||||
flags.BoolVar(&opts.short, "short", false, "Shows only Compose's version number.")
|
||||
flags.BoolVar(&opts.short, "short", false, "Shows only Compose's version number")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runVersion(opts versionOptions, dockerCli command.Cli) {
|
||||
if opts.short {
|
||||
fmt.Fprintln(dockerCli.Out(), strings.TrimPrefix(internal.Version, "v"))
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), strings.TrimPrefix(internal.Version, "v"))
|
||||
return
|
||||
}
|
||||
if opts.format == formatter.JSON {
|
||||
fmt.Fprintf(dockerCli.Out(), "{\"version\":%q}\n", internal.Version)
|
||||
_, _ = fmt.Fprintf(dockerCli.Out(), "{\"version\":%q}\n", internal.Version)
|
||||
return
|
||||
}
|
||||
fmt.Fprintln(dockerCli.Out(), "Docker Compose version", internal.Version)
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), "Docker Compose version", internal.Version)
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
|
||||
func runViz(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *vizOptions) error {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "viz command is EXPERIMENTAL")
|
||||
project, err := opts.ToProject(dockerCli, nil)
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPreferredIndentationStr(t *testing.T) {
|
||||
@@ -83,10 +83,12 @@ func TestPreferredIndentationStr(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := preferredIndentationStr(tt.args.size, tt.args.useSpace)
|
||||
if tt.wantErr && assert.NotNilf(t, err, fmt.Sprintf("preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)) {
|
||||
return
|
||||
if tt.wantErr {
|
||||
require.Errorf(t, err, "preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
assert.Equalf(t, tt.want, got, "preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)
|
||||
}
|
||||
assert.Equalf(t, tt.want, got, "preferredIndentationStr(%v, %v)", tt.args.size, tt.args.useSpace)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
var err error
|
||||
cmd := &cobra.Command{
|
||||
Use: "wait SERVICE [SERVICE...] [OPTIONS]",
|
||||
Short: "Block until the first service container stops",
|
||||
Short: "Block until containers of all (or specified) services stop.",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: Adapt(func(ctx context.Context, services []string) error {
|
||||
opts.services = services
|
||||
@@ -61,7 +61,7 @@ func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
func runWait(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *waitOptions) (int64, error) {
|
||||
_, name, err := opts.projectOrName(dockerCli)
|
||||
_, name, err := opts.projectOrName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/internal/locker"
|
||||
@@ -31,7 +32,7 @@ import (
|
||||
|
||||
type watchOptions struct {
|
||||
*ProjectOptions
|
||||
quiet bool
|
||||
prune bool
|
||||
noUp bool
|
||||
}
|
||||
|
||||
@@ -57,13 +58,14 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&watchOpts.quiet, "quiet", false, "hide build output")
|
||||
cmd.Flags().BoolVar(&buildOpts.quiet, "quiet", false, "hide build output")
|
||||
cmd.Flags().BoolVar(&watchOpts.prune, "prune", false, "Prune dangling images on rebuild")
|
||||
cmd.Flags().BoolVar(&watchOpts.noUp, "no-up", false, "Do not build & start services before watching")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, watchOpts watchOptions, buildOpts buildOptions, services []string) error {
|
||||
project, err := watchOpts.ToProject(dockerCli, nil)
|
||||
project, _, err := watchOpts.ToProject(ctx, dockerCli, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -101,20 +103,23 @@ func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, w
|
||||
Recreate: api.RecreateDiverged,
|
||||
RecreateDependencies: api.RecreateNever,
|
||||
Inherit: true,
|
||||
QuietPull: watchOpts.quiet,
|
||||
QuietPull: buildOpts.quiet,
|
||||
},
|
||||
Start: api.StartOptions{
|
||||
Project: project,
|
||||
Attach: nil,
|
||||
CascadeStop: false,
|
||||
Services: services,
|
||||
Project: project,
|
||||
Attach: nil,
|
||||
Services: services,
|
||||
},
|
||||
}
|
||||
if err := backend.Up(ctx, project, upOpts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), false, false, false)
|
||||
return backend.Watch(ctx, project, services, api.WatchOptions{
|
||||
Build: build,
|
||||
Build: &build,
|
||||
LogTo: consumer,
|
||||
Prune: watchOpts.prune,
|
||||
})
|
||||
}
|
||||
|
||||
107
cmd/formatter/ansi.go
Normal file
107
cmd/formatter/ansi.go
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright 2024 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
)
|
||||
|
||||
var disableAnsi bool
|
||||
|
||||
func ansi(code string) string {
|
||||
return fmt.Sprintf("\033%s", code)
|
||||
}
|
||||
|
||||
func SaveCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("7"))
|
||||
}
|
||||
|
||||
func RestoreCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("8"))
|
||||
}
|
||||
|
||||
func HideCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("[?25l"))
|
||||
}
|
||||
|
||||
func ShowCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("[?25h"))
|
||||
}
|
||||
|
||||
func MoveCursor(y, x int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi(fmt.Sprintf("[%d;%dH", y, x)))
|
||||
}
|
||||
|
||||
func MoveCursorX(pos int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dG", pos)))
|
||||
}
|
||||
|
||||
func ClearLine() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
// Does not move cursor from its current position
|
||||
fmt.Print(ansi("[2K"))
|
||||
}
|
||||
|
||||
func MoveCursorUp(lines int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
// Does not add new lines
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dA", lines)))
|
||||
}
|
||||
|
||||
func MoveCursorDown(lines int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
// Does not add new lines
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dB", lines)))
|
||||
}
|
||||
|
||||
func NewLine() {
|
||||
// Like \n
|
||||
fmt.Print("\012")
|
||||
}
|
||||
|
||||
func lenAnsi(s string) int {
|
||||
// len has into consideration ansi codes, if we want
|
||||
// the len of the actual len(string) we need to strip
|
||||
// all ansi codes
|
||||
return len(stripansi.Strip(s))
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/cli/cli/command"
|
||||
)
|
||||
|
||||
var names = []string{
|
||||
@@ -35,6 +35,18 @@ var names = []string{
|
||||
"white",
|
||||
}
|
||||
|
||||
const (
|
||||
BOLD = "1"
|
||||
FAINT = "2"
|
||||
ITALIC = "3"
|
||||
UNDERLINE = "4"
|
||||
)
|
||||
|
||||
const (
|
||||
RESET = "0"
|
||||
CYAN = "36"
|
||||
)
|
||||
|
||||
const (
|
||||
// Never use ANSI codes
|
||||
Never = "never"
|
||||
@@ -47,15 +59,16 @@ const (
|
||||
)
|
||||
|
||||
// SetANSIMode configure formatter for colored output on ANSI-compliant console
|
||||
func SetANSIMode(streams api.Streams, ansi string) {
|
||||
func SetANSIMode(streams command.Streams, ansi string) {
|
||||
if !useAnsi(streams, ansi) {
|
||||
nextColor = func() colorFunc {
|
||||
return monochrome
|
||||
}
|
||||
disableAnsi = true
|
||||
}
|
||||
}
|
||||
|
||||
func useAnsi(streams api.Streams, ansi string) bool {
|
||||
func useAnsi(streams command.Streams, ansi string) bool {
|
||||
switch ansi {
|
||||
case Always:
|
||||
return true
|
||||
@@ -72,12 +85,17 @@ var monochrome = func(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
func ansiColor(code, s string) string {
|
||||
return fmt.Sprintf("%s%s%s", ansi(code), s, ansi("0"))
|
||||
func ansiColor(code, s string, formatOpts ...string) string {
|
||||
return fmt.Sprintf("%s%s%s", ansiColorCode(code, formatOpts...), s, ansiColorCode("0"))
|
||||
}
|
||||
|
||||
func ansi(code string) string {
|
||||
return fmt.Sprintf("\033[%sm", code)
|
||||
// Everything about ansiColorCode color https://hyperskill.org/learn/step/18193
|
||||
func ansiColorCode(code string, formatOpts ...string) string {
|
||||
res := "\033["
|
||||
for _, c := range formatOpts {
|
||||
res = fmt.Sprintf("%s%s;", res, c)
|
||||
}
|
||||
return fmt.Sprintf("%s%sm", res, code)
|
||||
}
|
||||
|
||||
func makeColorFunc(code string) colorFunc {
|
||||
@@ -86,10 +104,12 @@ func makeColorFunc(code string) colorFunc {
|
||||
}
|
||||
}
|
||||
|
||||
var nextColor = rainbowColor
|
||||
var rainbow []colorFunc
|
||||
var currentIndex = 0
|
||||
var mutex sync.Mutex
|
||||
var (
|
||||
nextColor = rainbowColor
|
||||
rainbow []colorFunc
|
||||
currentIndex = 0
|
||||
mutex sync.Mutex
|
||||
)
|
||||
|
||||
func rainbowColor() colorFunc {
|
||||
mutex.Lock()
|
||||
|
||||
@@ -33,6 +33,7 @@ const (
|
||||
defaultContainerTableFormat = "table {{.Name}}\t{{.Image}}\t{{.Command}}\t{{.Service}}\t{{.RunningFor}}\t{{.Status}}\t{{.Ports}}"
|
||||
|
||||
nameHeader = "NAME"
|
||||
projectHeader = "PROJECT"
|
||||
serviceHeader = "SERVICE"
|
||||
commandHeader = "COMMAND"
|
||||
runningForHeader = "CREATED"
|
||||
@@ -112,6 +113,7 @@ func NewContainerContext() *ContainerContext {
|
||||
containerCtx.Header = formatter.SubHeaderContext{
|
||||
"ID": formatter.ContainerIDHeader,
|
||||
"Name": nameHeader,
|
||||
"Project": projectHeader,
|
||||
"Service": serviceHeader,
|
||||
"Image": formatter.ImageHeader,
|
||||
"Command": commandHeader,
|
||||
@@ -164,6 +166,10 @@ func (c *ContainerContext) Service() string {
|
||||
return c.c.Service
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Project() string {
|
||||
return c.c.Project
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Image() string {
|
||||
return c.c.Image
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/buger/goterm"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
)
|
||||
@@ -60,17 +61,32 @@ func (l *logConsumer) Register(name string) {
|
||||
}
|
||||
|
||||
func (l *logConsumer) register(name string) *presenter {
|
||||
cf := monochrome
|
||||
if l.color {
|
||||
cf = nextColor()
|
||||
}
|
||||
p := &presenter{
|
||||
colors: cf,
|
||||
name: name,
|
||||
var p *presenter
|
||||
root, _, found := strings.Cut(name, " ")
|
||||
if found {
|
||||
parent := l.getPresenter(root)
|
||||
p = &presenter{
|
||||
colors: parent.colors,
|
||||
name: name,
|
||||
prefix: parent.prefix,
|
||||
}
|
||||
} else {
|
||||
cf := monochrome
|
||||
if l.color {
|
||||
if name == api.WatchLogger {
|
||||
cf = makeColorFunc("92")
|
||||
} else {
|
||||
cf = nextColor()
|
||||
}
|
||||
}
|
||||
p = &presenter{
|
||||
colors: cf,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
l.presenters.Store(name, p)
|
||||
l.computeWidth()
|
||||
if l.prefix {
|
||||
l.computeWidth()
|
||||
l.presenters.Range(func(key, value interface{}) bool {
|
||||
p := value.(*presenter)
|
||||
p.setPrefix(l.width)
|
||||
@@ -93,7 +109,7 @@ func (l *logConsumer) Log(container, message string) {
|
||||
l.write(l.stdout, container, message)
|
||||
}
|
||||
|
||||
// Log formats a log message as received from name/container
|
||||
// Err formats a log message as received from name/container
|
||||
func (l *logConsumer) Err(container, message string) {
|
||||
l.write(l.stderr, container, message)
|
||||
}
|
||||
@@ -102,20 +118,28 @@ func (l *logConsumer) write(w io.Writer, container, message string) {
|
||||
if l.ctx.Err() != nil {
|
||||
return
|
||||
}
|
||||
if KeyboardManager != nil {
|
||||
KeyboardManager.ClearKeyboardInfo()
|
||||
}
|
||||
|
||||
p := l.getPresenter(container)
|
||||
timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
|
||||
for _, line := range strings.Split(message, "\n") {
|
||||
if l.timestamp {
|
||||
fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
|
||||
_, _ = fmt.Fprintf(w, "%s%s%s\n", p.prefix, timestamp, line)
|
||||
} else {
|
||||
fmt.Fprintf(w, "%s%s\n", p.prefix, line)
|
||||
_, _ = fmt.Fprintf(w, "%s%s\n", p.prefix, line)
|
||||
}
|
||||
}
|
||||
|
||||
if KeyboardManager != nil {
|
||||
KeyboardManager.PrintKeyboardInfo()
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logConsumer) Status(container, msg string) {
|
||||
p := l.getPresenter(container)
|
||||
s := p.colors(fmt.Sprintf("%s %s\n", container, msg))
|
||||
s := p.colors(fmt.Sprintf("%s%s %s\n", goterm.RESET_LINE, container, msg))
|
||||
l.stdout.Write([]byte(s)) //nolint:errcheck
|
||||
}
|
||||
|
||||
@@ -138,5 +162,9 @@ type presenter struct {
|
||||
}
|
||||
|
||||
func (p *presenter) setPrefix(width int) {
|
||||
if p.name == api.WatchLogger {
|
||||
p.prefix = p.colors(strings.Repeat(" ", width) + " ⦿ ")
|
||||
return
|
||||
}
|
||||
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
|
||||
}
|
||||
|
||||
387
cmd/formatter/shortcut.go
Normal file
387
cmd/formatter/shortcut.go
Normal file
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
Copyright 2024 Docker Compose CLI authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/buger/goterm"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/watch"
|
||||
"github.com/eiannone/keyboard"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
)
|
||||
|
||||
const DISPLAY_ERROR_TIME = 10
|
||||
|
||||
type KeyboardError struct {
|
||||
err error
|
||||
timeStart time.Time
|
||||
}
|
||||
|
||||
func (ke *KeyboardError) shouldDisplay() bool {
|
||||
return ke.err != nil && int(time.Since(ke.timeStart).Seconds()) < DISPLAY_ERROR_TIME
|
||||
}
|
||||
|
||||
func (ke *KeyboardError) printError(height int, info string) {
|
||||
if ke.shouldDisplay() {
|
||||
errMessage := ke.err.Error()
|
||||
|
||||
MoveCursor(height-1-extraLines(info)-extraLines(errMessage), 0)
|
||||
ClearLine()
|
||||
|
||||
fmt.Print(errMessage)
|
||||
}
|
||||
}
|
||||
|
||||
func (ke *KeyboardError) addError(prefix string, err error) {
|
||||
ke.timeStart = time.Now()
|
||||
|
||||
prefix = ansiColor(CYAN, fmt.Sprintf("%s →", prefix), BOLD)
|
||||
errorString := fmt.Sprintf("%s %s", prefix, err.Error())
|
||||
|
||||
ke.err = errors.New(errorString)
|
||||
}
|
||||
|
||||
func (ke *KeyboardError) error() string {
|
||||
return ke.err.Error()
|
||||
}
|
||||
|
||||
type KeyboardWatch struct {
|
||||
Watcher watch.Notify
|
||||
Watching bool
|
||||
WatchFn func(ctx context.Context, doneCh chan bool, project *types.Project, services []string, options api.WatchOptions) error
|
||||
Ctx context.Context
|
||||
Cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func (kw *KeyboardWatch) isWatching() bool {
|
||||
return kw.Watching
|
||||
}
|
||||
|
||||
func (kw *KeyboardWatch) switchWatching() {
|
||||
kw.Watching = !kw.Watching
|
||||
}
|
||||
|
||||
func (kw *KeyboardWatch) newContext(ctx context.Context) context.CancelFunc {
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
kw.Ctx = ctx
|
||||
kw.Cancel = cancel
|
||||
return cancel
|
||||
}
|
||||
|
||||
type KEYBOARD_LOG_LEVEL int
|
||||
|
||||
const (
|
||||
NONE KEYBOARD_LOG_LEVEL = 0
|
||||
INFO KEYBOARD_LOG_LEVEL = 1
|
||||
DEBUG KEYBOARD_LOG_LEVEL = 2
|
||||
)
|
||||
|
||||
type LogKeyboard struct {
|
||||
kError KeyboardError
|
||||
Watch KeyboardWatch
|
||||
IsDockerDesktopActive bool
|
||||
IsWatchConfigured bool
|
||||
logLevel KEYBOARD_LOG_LEVEL
|
||||
signalChannel chan<- os.Signal
|
||||
}
|
||||
|
||||
var (
|
||||
KeyboardManager *LogKeyboard
|
||||
eg multierror.Group
|
||||
)
|
||||
|
||||
func NewKeyboardManager(ctx context.Context, isDockerDesktopActive, isWatchConfigured bool,
|
||||
sc chan<- os.Signal,
|
||||
watchFn func(ctx context.Context,
|
||||
doneCh chan bool,
|
||||
project *types.Project,
|
||||
services []string,
|
||||
options api.WatchOptions,
|
||||
) error,
|
||||
) {
|
||||
km := LogKeyboard{}
|
||||
km.IsDockerDesktopActive = isDockerDesktopActive
|
||||
km.IsWatchConfigured = isWatchConfigured
|
||||
km.logLevel = INFO
|
||||
|
||||
km.Watch.Watching = false
|
||||
km.Watch.WatchFn = watchFn
|
||||
|
||||
km.signalChannel = sc
|
||||
|
||||
KeyboardManager = &km
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) ClearKeyboardInfo() {
|
||||
lk.clearNavigationMenu()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) PrintKeyboardInfo() {
|
||||
if lk.logLevel == INFO {
|
||||
lk.printNavigationMenu()
|
||||
}
|
||||
}
|
||||
|
||||
// Creates space to print error and menu string
|
||||
func (lk *LogKeyboard) createBuffer(lines int) {
|
||||
if lk.kError.shouldDisplay() {
|
||||
extraLines := extraLines(lk.kError.error()) + 1
|
||||
lines += extraLines
|
||||
}
|
||||
|
||||
// get the string
|
||||
infoMessage := lk.navigationMenu()
|
||||
// calculate how many lines we need to display the menu info
|
||||
// might be needed a line break
|
||||
extraLines := extraLines(infoMessage) + 1
|
||||
lines += extraLines
|
||||
|
||||
if lines > 0 {
|
||||
allocateSpace(lines)
|
||||
MoveCursorUp(lines)
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) printNavigationMenu() {
|
||||
offset := 1
|
||||
lk.clearNavigationMenu()
|
||||
lk.createBuffer(offset)
|
||||
|
||||
if lk.logLevel == INFO {
|
||||
height := goterm.Height()
|
||||
menu := lk.navigationMenu()
|
||||
|
||||
MoveCursorX(0)
|
||||
SaveCursor()
|
||||
|
||||
lk.kError.printError(height, menu)
|
||||
|
||||
MoveCursor(height-extraLines(menu), 0)
|
||||
ClearLine()
|
||||
fmt.Print(menu)
|
||||
|
||||
MoveCursorX(0)
|
||||
RestoreCursor()
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) navigationMenu() string {
|
||||
var openDDInfo string
|
||||
if lk.IsDockerDesktopActive {
|
||||
openDDInfo = shortcutKeyColor("v") + navColor(" View in Docker Desktop")
|
||||
}
|
||||
|
||||
var openDDUI string
|
||||
if openDDInfo != "" {
|
||||
openDDUI = navColor(" ")
|
||||
}
|
||||
if lk.IsDockerDesktopActive {
|
||||
openDDUI = openDDUI + shortcutKeyColor("o") + navColor(" View Config")
|
||||
}
|
||||
|
||||
var watchInfo string
|
||||
if openDDInfo != "" || openDDUI != "" {
|
||||
watchInfo = navColor(" ")
|
||||
}
|
||||
isEnabled := " Enable"
|
||||
if lk.Watch.Watching {
|
||||
isEnabled = " Disable"
|
||||
}
|
||||
watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch")
|
||||
return openDDInfo + openDDUI + watchInfo
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) clearNavigationMenu() {
|
||||
height := goterm.Height()
|
||||
MoveCursorX(0)
|
||||
SaveCursor()
|
||||
|
||||
// ClearLine()
|
||||
for i := 0; i < height; i++ {
|
||||
MoveCursorDown(1)
|
||||
ClearLine()
|
||||
}
|
||||
RestoreCursor()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) openDockerDesktop(ctx context.Context, project *types.Project) {
|
||||
if !lk.IsDockerDesktopActive {
|
||||
return
|
||||
}
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/apps/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not open Docker Desktop")
|
||||
lk.keyboardError("View", err)
|
||||
}
|
||||
return err
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) openDDComposeUI(ctx context.Context, project *types.Project) {
|
||||
if !lk.IsDockerDesktopActive {
|
||||
return
|
||||
}
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/composeview", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("View Config", err)
|
||||
}
|
||||
return err
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) openDDWatchDocs(ctx context.Context, project *types.Project) {
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/gui/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
link := fmt.Sprintf("docker-desktop://dashboard/docker-compose/%s/watch", project.Name)
|
||||
err := open.Run(link)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not open Docker Desktop Compose UI")
|
||||
lk.keyboardError("Watch Docs", err)
|
||||
}
|
||||
return err
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) keyboardError(prefix string, err error) {
|
||||
lk.kError.addError(prefix, err)
|
||||
|
||||
lk.printNavigationMenu()
|
||||
timer1 := time.NewTimer((DISPLAY_ERROR_TIME + 1) * time.Second)
|
||||
go func() {
|
||||
<-timer1.C
|
||||
lk.printNavigationMenu()
|
||||
}()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) StartWatch(ctx context.Context, doneCh chan bool, project *types.Project, options api.UpOptions) {
|
||||
if !lk.IsWatchConfigured {
|
||||
return
|
||||
}
|
||||
lk.Watch.switchWatching()
|
||||
if !lk.Watch.isWatching() {
|
||||
lk.Watch.Cancel()
|
||||
} else {
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
if options.Create.Build == nil {
|
||||
err := fmt.Errorf("Cannot run watch mode with flag --no-build")
|
||||
lk.keyboardError("Watch", err)
|
||||
return err
|
||||
}
|
||||
|
||||
lk.Watch.newContext(ctx)
|
||||
buildOpts := *options.Create.Build
|
||||
buildOpts.Quiet = true
|
||||
err := lk.Watch.WatchFn(lk.Watch.Ctx, doneCh, project, options.Start.Services, api.WatchOptions{
|
||||
Build: &buildOpts,
|
||||
LogTo: options.Start.Attach,
|
||||
})
|
||||
if err != nil {
|
||||
lk.Watch.switchWatching()
|
||||
options.Start.Attach.Err(api.WatchLogger, err.Error())
|
||||
}
|
||||
return err
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) HandleKeyEvents(event keyboard.KeyEvent, ctx context.Context, doneCh chan bool, project *types.Project, options api.UpOptions) {
|
||||
switch kRune := event.Rune; kRune {
|
||||
case 'v':
|
||||
lk.openDockerDesktop(ctx, project)
|
||||
case 'w':
|
||||
if !lk.IsWatchConfigured {
|
||||
// we try to open watch docs if DD is installed
|
||||
if lk.IsDockerDesktopActive {
|
||||
lk.openDDWatchDocs(ctx, project)
|
||||
}
|
||||
// either way we mark menu/watch as an error
|
||||
eg.Go(tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
err := fmt.Errorf("watch is not yet configured. Learn more: %s", ansiColor(CYAN, "https://docs.docker.com/compose/file-watch/"))
|
||||
lk.keyboardError("Watch", err)
|
||||
return err
|
||||
}))
|
||||
return
|
||||
}
|
||||
lk.StartWatch(ctx, doneCh, project, options)
|
||||
case 'o':
|
||||
lk.openDDComposeUI(ctx, project)
|
||||
}
|
||||
switch key := event.Key; key {
|
||||
case keyboard.KeyCtrlC:
|
||||
_ = keyboard.Close()
|
||||
lk.clearNavigationMenu()
|
||||
ShowCursor()
|
||||
|
||||
lk.logLevel = NONE
|
||||
if lk.Watch.Watching && lk.Watch.Cancel != nil {
|
||||
lk.Watch.Cancel()
|
||||
_ = eg.Wait().ErrorOrNil() // Need to print this ?
|
||||
}
|
||||
// will notify main thread to kill and will handle gracefully
|
||||
lk.signalChannel <- syscall.SIGINT
|
||||
case keyboard.KeyEnter:
|
||||
NewLine()
|
||||
lk.printNavigationMenu()
|
||||
}
|
||||
}
|
||||
|
||||
func allocateSpace(lines int) {
|
||||
for i := 0; i < lines; i++ {
|
||||
ClearLine()
|
||||
NewLine()
|
||||
MoveCursorX(0)
|
||||
}
|
||||
}
|
||||
|
||||
func extraLines(s string) int {
|
||||
return int(math.Floor(float64(lenAnsi(s)) / float64(goterm.Width())))
|
||||
}
|
||||
|
||||
func shortcutKeyColor(key string) string {
|
||||
foreground := "38;2"
|
||||
black := "0;0;0"
|
||||
background := "48;2"
|
||||
white := "255;255;255"
|
||||
return ansiColor(foreground+";"+black+";"+background+";"+white, key, BOLD)
|
||||
}
|
||||
|
||||
func navColor(key string) string {
|
||||
return ansiColor(FAINT, key)
|
||||
}
|
||||
41
cmd/main.go
41
cmd/main.go
@@ -24,31 +24,38 @@ import (
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/cmdtrace"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/compatibility"
|
||||
commands "github.com/docker/compose/v2/cmd/compose"
|
||||
"github.com/docker/compose/v2/internal"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
)
|
||||
|
||||
func pluginMain() {
|
||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
||||
serviceProxy := api.NewServiceProxy().WithService(compose.NewComposeService(dockerCli))
|
||||
cmd := commands.RootCommand(dockerCli, serviceProxy)
|
||||
originalPreRun := cmd.PersistentPreRunE
|
||||
// TODO(milas): this cast is safe but we should not need to do this,
|
||||
// we should expose the concrete service type so that we do not need
|
||||
// to rely on the `api.Service` interface internally
|
||||
backend := compose.NewComposeService(dockerCli).(commands.Backend)
|
||||
cmd := commands.RootCommand(dockerCli, backend)
|
||||
originalPreRunE := cmd.PersistentPreRunE
|
||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
// initialize the dockerCli instance
|
||||
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO(milas): add an env var to enable logging from the
|
||||
// OTel components for debugging purposes
|
||||
_ = cmdtrace.Setup(cmd, dockerCli, os.Args[1:])
|
||||
// compose-specific initialization
|
||||
dockerCliPostInitialize(dockerCli)
|
||||
|
||||
if originalPreRun != nil {
|
||||
return originalPreRun(cmd, args)
|
||||
if err := cmdtrace.Setup(cmd, dockerCli, os.Args[1:]); err != nil {
|
||||
logrus.Debugf("failed to enable tracing: %v", err)
|
||||
}
|
||||
|
||||
if originalPreRunE != nil {
|
||||
return originalPreRunE(cmd, args)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -68,6 +75,22 @@ func pluginMain() {
|
||||
})
|
||||
}
|
||||
|
||||
// dockerCliPostInitialize performs Compose-specific configuration for the
|
||||
// command.Cli instance provided by the plugin.Run() initialization.
|
||||
//
|
||||
// NOTE: This must be called AFTER plugin.PersistentPreRunE.
|
||||
func dockerCliPostInitialize(dockerCli command.Cli) {
|
||||
// HACK(milas): remove once docker/cli#4574 is merged; for now,
|
||||
// set it in a rather roundabout way by grabbing the underlying
|
||||
// concrete client and manually invoking an option on it
|
||||
_ = dockerCli.Apply(func(cli *command.DockerCli) error {
|
||||
if mobyClient, ok := cli.Client().(*client.Client); ok {
|
||||
_ = client.WithUserAgent("compose/" + internal.Version)(mobyClient)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func main() {
|
||||
if plugin.RunningStandalone() {
|
||||
os.Args = append([]string{"docker"}, compatibility.Convert(os.Args[1:])...)
|
||||
|
||||
@@ -1,64 +1,70 @@
|
||||
# docker compose
|
||||
|
||||
```text
|
||||
docker compose [-f <arg>...] [options] [COMMAND] [ARGS...]
|
||||
```
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Define and run multi-container applications with Docker.
|
||||
Define and run multi-container applications with Docker
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:--------------------------------|:--------------------------------------------------------------------------------------|
|
||||
| [`build`](compose_build.md) | Build or rebuild services |
|
||||
| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format |
|
||||
| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
|
||||
| [`create`](compose_create.md) | Creates containers for a service. |
|
||||
| [`down`](compose_down.md) | Stop and remove containers, networks |
|
||||
| [`events`](compose_events.md) | Receive real time events from containers. |
|
||||
| [`exec`](compose_exec.md) | Execute a command in a running container. |
|
||||
| [`images`](compose_images.md) | List images used by the created containers |
|
||||
| [`kill`](compose_kill.md) | Force stop service containers. |
|
||||
| [`logs`](compose_logs.md) | View output from containers |
|
||||
| [`ls`](compose_ls.md) | List running compose projects |
|
||||
| [`pause`](compose_pause.md) | Pause services |
|
||||
| [`port`](compose_port.md) | Print the public port for a port binding. |
|
||||
| [`ps`](compose_ps.md) | List containers |
|
||||
| [`pull`](compose_pull.md) | Pull service images |
|
||||
| [`push`](compose_push.md) | Push service images |
|
||||
| [`restart`](compose_restart.md) | Restart service containers |
|
||||
| [`rm`](compose_rm.md) | Removes stopped service containers |
|
||||
| [`run`](compose_run.md) | Run a one-off command on a service. |
|
||||
| [`scale`](compose_scale.md) | Scale services |
|
||||
| [`start`](compose_start.md) | Start services |
|
||||
| [`stop`](compose_stop.md) | Stop services |
|
||||
| [`top`](compose_top.md) | Display the running processes |
|
||||
| [`unpause`](compose_unpause.md) | Unpause services |
|
||||
| [`up`](compose_up.md) | Create and start containers |
|
||||
| [`version`](compose_version.md) | Show the Docker Compose version information |
|
||||
| [`wait`](compose_wait.md) | Block until the first service container stops |
|
||||
| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated |
|
||||
| Name | Description |
|
||||
|:--------------------------------|:----------------------------------------------------------------------------------------|
|
||||
| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container |
|
||||
| [`build`](compose_build.md) | Build or rebuild services |
|
||||
| [`commit`](compose_commit.md) | Create a new image from a service container's changes |
|
||||
| [`config`](compose_config.md) | Parse, resolve and render compose file in canonical format |
|
||||
| [`cp`](compose_cp.md) | Copy files/folders between a service container and the local filesystem |
|
||||
| [`create`](compose_create.md) | Creates containers for a service |
|
||||
| [`down`](compose_down.md) | Stop and remove containers, networks |
|
||||
| [`events`](compose_events.md) | Receive real time events from containers |
|
||||
| [`exec`](compose_exec.md) | Execute a command in a running container |
|
||||
| [`export`](compose_export.md) | Export a service container's filesystem as a tar archive |
|
||||
| [`images`](compose_images.md) | List images used by the created containers |
|
||||
| [`kill`](compose_kill.md) | Force stop service containers |
|
||||
| [`logs`](compose_logs.md) | View output from containers |
|
||||
| [`ls`](compose_ls.md) | List running compose projects |
|
||||
| [`pause`](compose_pause.md) | Pause services |
|
||||
| [`port`](compose_port.md) | Print the public port for a port binding |
|
||||
| [`ps`](compose_ps.md) | List containers |
|
||||
| [`pull`](compose_pull.md) | Pull service images |
|
||||
| [`push`](compose_push.md) | Push service images |
|
||||
| [`restart`](compose_restart.md) | Restart service containers |
|
||||
| [`rm`](compose_rm.md) | Removes stopped service containers |
|
||||
| [`run`](compose_run.md) | Run a one-off command on a service |
|
||||
| [`scale`](compose_scale.md) | Scale services |
|
||||
| [`start`](compose_start.md) | Start services |
|
||||
| [`stats`](compose_stats.md) | Display a live stream of container(s) resource usage statistics |
|
||||
| [`stop`](compose_stop.md) | Stop services |
|
||||
| [`top`](compose_top.md) | Display the running processes |
|
||||
| [`unpause`](compose_unpause.md) | Unpause services |
|
||||
| [`up`](compose_up.md) | Create and start containers |
|
||||
| [`version`](compose_version.md) | Show the Docker Compose version information |
|
||||
| [`wait`](compose_wait.md) | Block until containers of all (or specified) services stop. |
|
||||
| [`watch`](compose_watch.md) | Watch build context for service and rebuild/refresh containers when files are updated |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-----------------------|:--------------|:--------|:----------------------------------------------------------------------------------------------------|
|
||||
| `--all-resources` | `bool` | | Include all resources, even those not used by services |
|
||||
| `--ansi` | `string` | `auto` | Control when to print ANSI control characters ("never"\|"always"\|"auto") |
|
||||
| `--compatibility` | | | Run compose in backward compatibility mode |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--env-file` | `stringArray` | | Specify an alternate environment file. |
|
||||
| `--compatibility` | `bool` | | Run compose in backward compatibility mode |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--env-file` | `stringArray` | | Specify an alternate environment file |
|
||||
| `-f`, `--file` | `stringArray` | | Compose configuration files |
|
||||
| `--parallel` | `int` | `-1` | Control max parallelism, -1 for unlimited |
|
||||
| `--profile` | `stringArray` | | Specify a profile to enable |
|
||||
| `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, quiet) |
|
||||
| `--progress` | `string` | `auto` | Set type of progress output (auto, tty, plain, json, quiet) |
|
||||
| `--project-directory` | `string` | | Specify an alternate working directory<br>(default: the path of the, first specified, Compose file) |
|
||||
| `-p`, `--project-name` | `string` | | Project name |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
You can use the compose subcommand, `docker compose [-f <arg>...] [options] [COMMAND] [ARGS...]`, to build and manage
|
||||
multiple services in Docker containers.
|
||||
## Examples
|
||||
|
||||
### Use `-f` to specify the name and path of one or more Compose files
|
||||
Use the `-f` flag to specify the location of a Compose configuration file.
|
||||
@@ -174,6 +180,9 @@ If flags are explicitly set on the command line, the associated environment vari
|
||||
Setting the `COMPOSE_IGNORE_ORPHANS` environment variable to `true` stops docker compose from detecting orphaned
|
||||
containers for the project.
|
||||
|
||||
Setting the `COMPOSE_MENU` environment variable to `false` disables the helper menu when running `docker compose up`
|
||||
in attached mode. Alternatively, you can also run `docker compose up --menu=false` to disable the helper menu.
|
||||
|
||||
### Use Dry Run mode to test your command
|
||||
|
||||
Use `--dry-run` flag to test a command without changing your application stack state.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# docker compose alpha dry-run
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Dry run command allows you to test a command without applying changes.
|
||||
Dry run command allows you to test a command without applying changes
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
17
docs/reference/compose_alpha_generate.md
Normal file
17
docs/reference/compose_alpha_generate.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# docker compose alpha generate
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
EXPERIMENTAL - Generate a Compose file from existing containers
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
|
||||
| `--name` | `string` | | Project name to set in the Compose file |
|
||||
| `--project-dir` | `string` | | Directory to use for the project |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -5,10 +5,11 @@ Publish compose application
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------------------|:-----|:--------|:--------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--resolve-image-digests` | | | Pin image tags to digests. |
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------------------|:---------|:--------|:-------------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--oci-version` | `string` | | OCI Image/Artifact specification version (automatically determined by default) |
|
||||
| `--resolve-image-digests` | `bool` | | Pin image tags to digests |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# docker compose alpha scale
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Scale services.
|
||||
Scale services
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-----|:--------|:--------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--no-deps` | | | Don't start linked services |
|
||||
| `--no-deps` | | | Don't start linked services |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -5,14 +5,14 @@ EXPERIMENTAL - Generate a graphviz graph from your compose file
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------|:------|:--------|:---------------------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--image` | | | Include service's image name in output graph |
|
||||
| `--indentation-size` | `int` | `1` | Number of tabs or spaces to use for indentation |
|
||||
| `--networks` | | | Include service's attached networks in output graph |
|
||||
| `--ports` | | | Include service's exposed ports in output graph |
|
||||
| `--spaces` | | | If given, space character ' ' will be used to indent,<br>otherwise tab character '\t' will be used |
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------|:-------|:--------|:---------------------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--image` | `bool` | | Include service's image name in output graph |
|
||||
| `--indentation-size` | `int` | `1` | Number of tabs or spaces to use for indentation |
|
||||
| `--networks` | `bool` | | Include service's attached networks in output graph |
|
||||
| `--ports` | `bool` | | Include service's exposed ports in output graph |
|
||||
| `--spaces` | `bool` | | If given, space character ' ' will be used to indent,<br>otherwise tab character '\t' will be used |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
17
docs/reference/compose_attach.md
Normal file
17
docs/reference/compose_attach.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# docker compose attach
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Attach local standard input, output, and error streams to a service's running container
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:----------------------------------------------------------|
|
||||
| `--detach-keys` | `string` | | Override the key sequence for detaching from a container. |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas. |
|
||||
| `--no-stdin` | `bool` | | Do not attach STDIN |
|
||||
| `--sig-proxy` | `bool` | `true` | Proxy all received signals to the process |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -1,28 +1,37 @@
|
||||
# docker compose build
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Build or rebuild services
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-----------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------|
|
||||
| `--build-arg` | `stringArray` | | Set build-time variables for services. |
|
||||
| `--builder` | `string` | | Set builder to use. |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. |
|
||||
| `--no-cache` | | | Do not use cache when building the image |
|
||||
| `--pull` | | | Always attempt to pull a newer version of the image. |
|
||||
| `--push` | | | Push service images. |
|
||||
| `-q`, `--quiet` | | | Don't print anything to STDOUT |
|
||||
| `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
Services are built once and then tagged, by default as `project_service`.
|
||||
Services are built once and then tagged, by default as `project-service`.
|
||||
|
||||
If the Compose file specifies an
|
||||
[image](https://github.com/compose-spec/compose-spec/blob/master/spec.md#image) name,
|
||||
the image is tagged with that name, substituting any variables beforehand. See
|
||||
[variable interpolation](https://github.com/compose-spec/compose-spec/blob/master/spec.md#interpolation).
|
||||
|
||||
If you change a service's `Dockerfile` or the contents of its build directory,
|
||||
run `docker compose build` to rebuild it.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------|
|
||||
| `--build-arg` | `stringArray` | | Set build-time variables for services |
|
||||
| `--builder` | `string` | | Set builder to use |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-m`, `--memory` | `bytes` | `0` | Set memory limit for the build container. Not supported by BuildKit. |
|
||||
| `--no-cache` | `bool` | | Do not use cache when building the image |
|
||||
| `--pull` | `bool` | | Always attempt to pull a newer version of the image |
|
||||
| `--push` | `bool` | | Push service images |
|
||||
| `-q`, `--quiet` | `bool` | | Don't print anything to STDOUT |
|
||||
| `--ssh` | `string` | | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
|
||||
| `--with-dependencies` | `bool` | | Also build dependencies (transitively) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
Services are built once and then tagged, by default as `project-service`.
|
||||
|
||||
If the Compose file specifies an
|
||||
[image](https://github.com/compose-spec/compose-spec/blob/master/spec.md#image) name,
|
||||
|
||||
19
docs/reference/compose_commit.md
Normal file
19
docs/reference/compose_commit.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# docker compose commit
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Create a new image from a service container's changes
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:---------|:--------|:-----------------------------------------------------------|
|
||||
| `-a`, `--author` | `string` | | Author (e.g., "John Hannibal Smith <hannibal@a-team.com>") |
|
||||
| `-c`, `--change` | `list` | | Apply Dockerfile instruction to the created image |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas. |
|
||||
| `-m`, `--message` | `string` | | Commit message |
|
||||
| `-p`, `--pause` | `bool` | `true` | Pause container during commit |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# docker compose convert
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Parse, resolve and render compose file in canonical format
|
||||
`docker compose config` renders the actual data model to be applied on the Docker Engine.
|
||||
It merges the Compose files set by `-f` flags, resolves variables in the Compose file, and expands short-notation into
|
||||
the canonical format.
|
||||
|
||||
### Aliases
|
||||
|
||||
@@ -11,20 +13,22 @@ Parse, resolve and render compose file in canonical format
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------------------|:---------|:--------|:----------------------------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--environment` | `bool` | | Print environment used for interpolation. |
|
||||
| `--format` | `string` | `yaml` | Format the output. Values: [yaml \| json] |
|
||||
| `--hash` | `string` | | Print the service config hash, one per line. |
|
||||
| `--images` | | | Print the image names, one per line. |
|
||||
| `--no-consistency` | | | Don't check model consistency - warning: may produce invalid Compose output |
|
||||
| `--no-interpolate` | | | Don't interpolate environment variables. |
|
||||
| `--no-normalize` | | | Don't normalize compose model. |
|
||||
| `--no-path-resolution` | | | Don't resolve file paths. |
|
||||
| `--images` | `bool` | | Print the image names, one per line. |
|
||||
| `--no-consistency` | `bool` | | Don't check model consistency - warning: may produce invalid Compose output |
|
||||
| `--no-interpolate` | `bool` | | Don't interpolate environment variables |
|
||||
| `--no-normalize` | `bool` | | Don't normalize compose model |
|
||||
| `--no-path-resolution` | `bool` | | Don't resolve file paths |
|
||||
| `-o`, `--output` | `string` | | Save to file (default to stdout) |
|
||||
| `--profiles` | | | Print the profile names, one per line. |
|
||||
| `-q`, `--quiet` | | | Only validate the configuration, don't print anything. |
|
||||
| `--resolve-image-digests` | | | Pin image tags to digests. |
|
||||
| `--services` | | | Print the service names, one per line. |
|
||||
| `--volumes` | | | Print the volume names, one per line. |
|
||||
| `--profiles` | `bool` | | Print the profile names, one per line. |
|
||||
| `-q`, `--quiet` | `bool` | | Only validate the configuration, don't print anything |
|
||||
| `--resolve-image-digests` | `bool` | | Pin image tags to digests |
|
||||
| `--services` | `bool` | | Print the service names, one per line. |
|
||||
| `--variables` | `bool` | | Print model variables and default values. |
|
||||
| `--volumes` | `bool` | | Print the volume names, one per line. |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -5,12 +5,13 @@ Copy files/folders between a service container and the local filesystem
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:------|:--------|:--------------------------------------------------------|
|
||||
| `-a`, `--archive` | | | Archive mode (copy all uid/gid information) |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-L`, `--follow-link` | | | Always follow symbol link in SRC_PATH |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:-------|:--------|:--------------------------------------------------------|
|
||||
| `--all` | `bool` | | Include containers created by the run command |
|
||||
| `-a`, `--archive` | `bool` | | Archive mode (copy all uid/gid information) |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-L`, `--follow-link` | `bool` | | Always follow symbol link in SRC_PATH |
|
||||
| `--index` | `int` | `0` | Index of the container if service has multiple replicas |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
# docker compose create
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Creates containers for a service.
|
||||
Creates containers for a service
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------|:--------------|:---------|:----------------------------------------------------------------------------------------------|
|
||||
| `--build` | | | Build images before starting containers. |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--force-recreate` | | | Recreate containers even if their configuration and image haven't changed. |
|
||||
| `--no-build` | | | Don't build an image, even if it's policy. |
|
||||
| `--no-recreate` | | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
||||
| `--build` | `bool` | | Build images before starting containers |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--force-recreate` | `bool` | | Recreate containers even if their configuration and image haven't changed |
|
||||
| `--no-build` | `bool` | | Don't build an image, even if it's policy |
|
||||
| `--no-recreate` | `bool` | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
||||
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never"\|"build") |
|
||||
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
|
||||
| `--quiet-pull` | `bool` | | Pull without printing progress information |
|
||||
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
|
||||
| `--scale` | `stringArray` | | Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present. |
|
||||
| `-y`, `--y` | `bool` | | Assume "yes" as answer to all prompts and run non-interactively |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,17 +1,29 @@
|
||||
# docker compose down
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Stop and remove containers, networks
|
||||
Stops containers and removes containers, networks, volumes, and images created by `up`.
|
||||
|
||||
By default, the only things removed are:
|
||||
|
||||
- Containers for services defined in the Compose file.
|
||||
- Networks defined in the networks section of the Compose file.
|
||||
- The default network, if one is used.
|
||||
|
||||
Networks and volumes defined as external are never removed.
|
||||
|
||||
Anonymous volumes are not removed by default. However, as they don’t have a stable name, they are not automatically
|
||||
mounted by a subsequent `up`. For data that needs to persist between updates, use explicit paths as bind mounts or
|
||||
named volumes.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
|
||||
| `--rmi` | `string` | | Remove images used by services. "local" remove only images that don't have a custom tag ("local"\|"all") |
|
||||
| `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds |
|
||||
| `-v`, `--volumes` | | | Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers. |
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------|:---------|:--------|:------------------------------------------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
|
||||
| `--rmi` | `string` | | Remove images used by services. "local" remove only images that don't have a custom tag ("local"\|"all") |
|
||||
| `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds |
|
||||
| `-v`, `--volumes` | `bool` | | Remove named volumes declared in the "volumes" section of the Compose file and anonymous volumes attached to containers |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,14 +1,32 @@
|
||||
# docker compose events
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Receive real time events from containers.
|
||||
Stream container events for every container in the project.
|
||||
|
||||
With the `--json` flag, a json object is printed one per line with the format:
|
||||
|
||||
```json
|
||||
{
|
||||
"time": "2015-11-20T18:01:03.615550",
|
||||
"type": "container",
|
||||
"action": "create",
|
||||
"id": "213cf7...5fc39a",
|
||||
"service": "web",
|
||||
"attributes": {
|
||||
"name": "application_web_1",
|
||||
"image": "alpine:edge"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The events that can be received using this can be seen [here](/reference/cli/docker/system/events/#object-types).
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-----|:--------|:------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--json` | | | Output events as a stream of json objects |
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-------|:--------|:------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--json` | `bool` | | Output events as a stream of json objects |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -33,4 +51,4 @@ With the `--json` flag, a json object is printed one per line with the format:
|
||||
}
|
||||
```
|
||||
|
||||
The events that can be received using this can be seen [here](https://docs.docker.com/engine/reference/commandline/events/#object-types).
|
||||
The events that can be received using this can be seen [here](https://docs.docker.com/reference/cli/docker/system/events/#object-types).
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
# docker compose exec
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Execute a command in a running container.
|
||||
This is the equivalent of `docker exec` targeting a Compose service.
|
||||
|
||||
With this subcommand, you can run arbitrary commands in your services. Commands allocate a TTY by default, so
|
||||
you can use a command such as `docker compose exec web sh` to get an interactive prompt.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:--------------|:--------|:---------------------------------------------------------------------------------|
|
||||
| `-d`, `--detach` | | | Detached mode: Run command in the background. |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-d`, `--detach` | `bool` | | Detached mode: Run command in the background |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
|
||||
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
|
||||
| `--privileged` | | | Give extended privileges to the process. |
|
||||
| `-u`, `--user` | `string` | | Run the command as this user. |
|
||||
| `-w`, `--workdir` | `string` | | Path to workdir directory for this command. |
|
||||
| `--index` | `int` | `0` | Index of the container if service has multiple replicas |
|
||||
| `-T`, `--no-TTY` | `bool` | `true` | Disable pseudo-TTY allocation. By default `docker compose exec` allocates a TTY. |
|
||||
| `--privileged` | `bool` | | Give extended privileges to the process |
|
||||
| `-u`, `--user` | `string` | | Run the command as this user |
|
||||
| `-w`, `--workdir` | `string` | | Path to workdir directory for this command |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
16
docs/reference/compose_export.md
Normal file
16
docs/reference/compose_export.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# docker compose export
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Export a service container's filesystem as a tar archive
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-----------------|:---------|:--------|:---------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas. |
|
||||
| `-o`, `--output` | `string` | | Write to a file, instead of STDOUT |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -5,11 +5,11 @@ List images used by the created containers
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:--------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--format` | `string` | `table` | Format the output. Values: [table \| json]. |
|
||||
| `-q`, `--quiet` | | | Only display IDs |
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:-------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--format` | `string` | `table` | Format the output. Values: [table \| json] |
|
||||
| `-q`, `--quiet` | `bool` | | Only display IDs |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
# docker compose kill
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Force stop service containers.
|
||||
Forces running containers to stop by sending a `SIGKILL` signal. Optionally the signal can be passed, for example:
|
||||
|
||||
```console
|
||||
$ docker compose kill -s SIGINT
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------|:---------|:----------|:----------------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
|
||||
| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container. |
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------|:---------|:----------|:---------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
|
||||
| `-s`, `--signal` | `string` | `SIGKILL` | SIGNAL to send to the container |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -19,5 +23,5 @@ Force stop service containers.
|
||||
Forces running containers to stop by sending a `SIGKILL` signal. Optionally the signal can be passed, for example:
|
||||
|
||||
```console
|
||||
$ docker-compose kill -s SIGINT
|
||||
$ docker compose kill -s SIGINT
|
||||
```
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
# docker compose logs
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
View output from containers
|
||||
Displays log output from services
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------|:---------|:--------|:-----------------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-f`, `--follow` | | | Follow log output. |
|
||||
| `--no-color` | | | Produce monochrome output. |
|
||||
| `--no-log-prefix` | | | Don't print prefix in logs. |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-f`, `--follow` | `bool` | | Follow log output |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
|
||||
| `--no-color` | `bool` | | Produce monochrome output |
|
||||
| `--no-log-prefix` | `bool` | | Don't print prefix in logs |
|
||||
| `--since` | `string` | | Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) |
|
||||
| `-n`, `--tail` | `string` | `all` | Number of lines to show from the end of the logs for each container. |
|
||||
| `-t`, `--timestamps` | | | Show timestamps. |
|
||||
| `-n`, `--tail` | `string` | `all` | Number of lines to show from the end of the logs for each container |
|
||||
| `-t`, `--timestamps` | `bool` | | Show timestamps |
|
||||
| `--until` | `string` | | Show logs before a timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes) |
|
||||
|
||||
|
||||
@@ -21,4 +22,4 @@ View output from containers
|
||||
|
||||
## Description
|
||||
|
||||
Displays log output from services.
|
||||
Displays log output from services
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
# docker compose ls
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
List running compose projects
|
||||
Lists running Compose projects
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:--------------------------------------------|
|
||||
| `-a`, `--all` | | | Show all stopped Compose projects |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--filter` | `filter` | | Filter output based on conditions provided. |
|
||||
| `--format` | `string` | `table` | Format the output. Values: [table \| json]. |
|
||||
| `-q`, `--quiet` | | | Only display IDs. |
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:-------------------------------------------|
|
||||
| `-a`, `--all` | `bool` | | Show all stopped Compose projects |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--filter` | `filter` | | Filter output based on conditions provided |
|
||||
| `--format` | `string` | `table` | Format the output. Values: [table \| json] |
|
||||
| `-q`, `--quiet` | `bool` | | Only display IDs |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
Lists running Compose projects.
|
||||
Lists running Compose projects
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# docker compose pause
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Pause services
|
||||
Pauses running containers of a service. They can be unpaused with `docker compose unpause`.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-----|:--------|:--------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-------|:--------|:--------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# docker compose port
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Print the public port for a port binding.
|
||||
Prints the public port for a port binding
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------|:---------|:--------|:--------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--index` | `int` | `0` | index of the container if service has multiple replicas |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--index` | `int` | `0` | Index of the container if service has multiple replicas |
|
||||
| `--protocol` | `string` | `tcp` | tcp or udp |
|
||||
|
||||
|
||||
@@ -16,4 +16,4 @@ Print the public port for a port binding.
|
||||
|
||||
## Description
|
||||
|
||||
Prints the public port for a port binding.
|
||||
Prints the public port for a port binding
|
||||
|
||||
@@ -1,19 +1,35 @@
|
||||
# docker compose ps
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
List containers
|
||||
Lists containers for a Compose project, with current status and exposed ports.
|
||||
|
||||
```console
|
||||
$ docker compose ps
|
||||
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
|
||||
example-foo-1 alpine "/entrypoint.…" foo 4 seconds ago Up 2 seconds 0.0.0.0:8080->80/tcp
|
||||
```
|
||||
|
||||
By default, only running containers are shown. `--all` flag can be used to include stopped containers.
|
||||
|
||||
```console
|
||||
$ docker compose ps --all
|
||||
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
|
||||
example-foo-1 alpine "/entrypoint.…" foo 4 seconds ago Up 2 seconds 0.0.0.0:8080->80/tcp
|
||||
example-bar-1 alpine "/entrypoint.…" bar 4 seconds ago exited (0)
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:--------------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `-a`, `--all` | | | Show all stopped containers (including those created by the run command) |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status). |
|
||||
| `-a`, `--all` | `bool` | | Show all stopped containers (including those created by the run command) |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| [`--filter`](#filter) | `string` | | Filter services by a property (supported filters: status) |
|
||||
| [`--format`](#format) | `string` | `table` | Format output using a custom template:<br>'table': Print output in table format with column headers (default)<br>'table TEMPLATE': Print output in table format using the given Go template<br>'json': Print in JSON format<br>'TEMPLATE': Print output using the given Go template.<br>Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates |
|
||||
| `--no-trunc` | | | Don't truncate output |
|
||||
| `-q`, `--quiet` | | | Only display IDs |
|
||||
| `--services` | | | Display services |
|
||||
| `--no-trunc` | `bool` | | Don't truncate output |
|
||||
| `--orphans` | `bool` | `true` | Include orphaned services (not declared by project) |
|
||||
| `-q`, `--quiet` | `bool` | | Only display IDs |
|
||||
| `--services` | `bool` | | Display services |
|
||||
| [`--status`](#status) | `stringArray` | | Filter services by status. Values: [paused \| restarting \| removing \| running \| dead \| created \| exited] |
|
||||
|
||||
|
||||
|
||||
@@ -1,26 +1,25 @@
|
||||
# docker compose pull
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Pull service images
|
||||
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on those images
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------------|:---------|:--------|:--------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--ignore-buildable` | | | Ignore images that can be built. |
|
||||
| `--ignore-pull-failures` | | | Pull what it can and ignores images with pull failures. |
|
||||
| `--include-deps` | | | Also pull services declared as dependencies. |
|
||||
| `--policy` | `string` | | Apply pull policy ("missing"\|"always"). |
|
||||
| `-q`, `--quiet` | | | Pull without printing progress information. |
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------------|:---------|:--------|:-------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--ignore-buildable` | `bool` | | Ignore images that can be built |
|
||||
| `--ignore-pull-failures` | `bool` | | Pull what it can and ignores images with pull failures |
|
||||
| `--include-deps` | `bool` | | Also pull services declared as dependencies |
|
||||
| `--policy` | `string` | | Apply pull policy ("missing"\|"always") |
|
||||
| `-q`, `--quiet` | `bool` | | Pull without printing progress information |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on
|
||||
those images.
|
||||
Pulls an image associated with a service defined in a `compose.yaml` file, but does not start containers based on those images
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1,16 +1,33 @@
|
||||
# docker compose push
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Push service images
|
||||
Pushes images for services to their respective registry/repository.
|
||||
|
||||
The following assumptions are made:
|
||||
- You are pushing an image you have built locally
|
||||
- You have access to the build key
|
||||
|
||||
Examples
|
||||
|
||||
```yaml
|
||||
services:
|
||||
service1:
|
||||
build: .
|
||||
image: localhost:5000/yourimage ## goes to local registry
|
||||
|
||||
service2:
|
||||
build: .
|
||||
image: your-dockerid/yourimage ## goes to your repository on Docker Hub
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------------|:-----|:--------|:-------------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--ignore-push-failures` | | | Push what it can and ignores images with push failures |
|
||||
| `--include-deps` | | | Also push images of services declared as dependencies |
|
||||
| `-q`, `--quiet` | | | Push without printing progress information |
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------------|:-------|:--------|:-------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--ignore-push-failures` | `bool` | | Push what it can and ignores images with push failures |
|
||||
| `--include-deps` | `bool` | | Also push images of services declared as dependencies |
|
||||
| `-q`, `--quiet` | `bool` | | Push without printing progress information |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
# docker compose restart
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Restart service containers
|
||||
Restarts all stopped and running services, or the specified services only.
|
||||
|
||||
If you make changes to your `compose.yml` configuration, these changes are not reflected
|
||||
after running this command. For example, changes to environment variables (which are added
|
||||
after a container is built, but before the container's command is executed) are not updated
|
||||
after restarting.
|
||||
|
||||
If you are looking to configure a service's restart policy, refer to
|
||||
[restart](https://github.com/compose-spec/compose-spec/blob/master/spec.md#restart)
|
||||
or [restart_policy](https://github.com/compose-spec/compose-spec/blob/master/deploy.md#restart_policy).
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:------|:--------|:--------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--no-deps` | | | Don't restart dependent services. |
|
||||
| `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds |
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:-------|:--------|:--------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--no-deps` | `bool` | | Don't restart dependent services |
|
||||
| `-t`, `--timeout` | `int` | `0` | Specify a shutdown timeout in seconds |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
# docker compose rm
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Removes stopped service containers
|
||||
Removes stopped service containers.
|
||||
|
||||
By default, anonymous volumes attached to containers will not be removed. You
|
||||
can override this with -v. To list all volumes, use "docker volume ls".
|
||||
By default, anonymous volumes attached to containers are not removed. You can override this with `-v`. To list all
|
||||
volumes, use `docker volume ls`.
|
||||
|
||||
Any data which is not in a volume will be lost.
|
||||
Any data which is not in a volume is lost.
|
||||
|
||||
Running the command with no options also removes one-off containers created by `docker compose run`:
|
||||
|
||||
```console
|
||||
$ docker compose rm
|
||||
Going to remove djangoquickstart_web_run_1
|
||||
Are you sure? [yN] y
|
||||
Removing djangoquickstart_web_run_1 ... done
|
||||
```
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:-----|:--------|:----------------------------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `-f`, `--force` | | | Don't ask to confirm removal |
|
||||
| `-s`, `--stop` | | | Stop the containers, if required, before removing |
|
||||
| `-v`, `--volumes` | | | Remove any anonymous volumes attached to containers |
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:-------|:--------|:----------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-f`, `--force` | `bool` | | Don't ask to confirm removal |
|
||||
| `-s`, `--stop` | `bool` | | Stop the containers, if required, before removing |
|
||||
| `-v`, `--volumes` | `bool` | | Remove any anonymous volumes attached to containers |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -1,33 +1,86 @@
|
||||
# docker compose run
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Run a one-off command on a service.
|
||||
Runs a one-time command against a service.
|
||||
|
||||
The following command starts the `web` service and runs `bash` as its command:
|
||||
|
||||
```console
|
||||
$ docker compose run web bash
|
||||
```
|
||||
|
||||
Commands you use with run start in new containers with configuration defined by that of the service,
|
||||
including volumes, links, and other details. However, there are two important differences:
|
||||
|
||||
First, the command passed by `run` overrides the command defined in the service configuration. For example, if the
|
||||
`web` service configuration is started with `bash`, then `docker compose run web python app.py` overrides it with
|
||||
`python app.py`.
|
||||
|
||||
The second difference is that the `docker compose run` command does not create any of the ports specified in the
|
||||
service configuration. This prevents port collisions with already-open ports. If you do want the service’s ports
|
||||
to be created and mapped to the host, specify the `--service-ports`
|
||||
|
||||
```console
|
||||
$ docker compose run --service-ports web python manage.py shell
|
||||
```
|
||||
|
||||
Alternatively, manual port mapping can be specified with the `--publish` or `-p` options, just as when using docker run:
|
||||
|
||||
```console
|
||||
$ docker compose run --publish 8080:80 -p 2022:22 -p 127.0.0.1:2021:21 web python manage.py shell
|
||||
```
|
||||
|
||||
If you start a service configured with links, the run command first checks to see if the linked service is running
|
||||
and starts the service if it is stopped. Once all the linked services are running, the run executes the command you
|
||||
passed it. For example, you could run:
|
||||
|
||||
```console
|
||||
$ docker compose run db psql -h db -U docker
|
||||
```
|
||||
|
||||
This opens an interactive PostgreSQL shell for the linked `db` container.
|
||||
|
||||
If you do not want the run command to start linked containers, use the `--no-deps` flag:
|
||||
|
||||
```console
|
||||
$ docker compose run --no-deps web python manage.py shell
|
||||
```
|
||||
|
||||
If you want to remove the container after running while overriding the container’s restart policy, use the `--rm` flag:
|
||||
|
||||
```console
|
||||
$ docker compose run --rm web python manage.py db upgrade
|
||||
```
|
||||
|
||||
This runs a database upgrade script, and removes the container when finished running, even if a restart policy is
|
||||
specified in the service configuration.
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:--------------|:--------|:----------------------------------------------------------------------------------|
|
||||
| `--build` | | | Build image before starting container. |
|
||||
| `--cap-add` | `list` | | Add Linux capabilities |
|
||||
| `--cap-drop` | `list` | | Drop Linux capabilities |
|
||||
| `-d`, `--detach` | | | Run container in background and print container ID |
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--entrypoint` | `string` | | Override the entrypoint of the image |
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
| `-i`, `--interactive` | | | Keep STDIN open even if not attached. |
|
||||
| `-l`, `--label` | `stringArray` | | Add or override a label |
|
||||
| `--name` | `string` | | Assign a name to the container |
|
||||
| `-T`, `--no-TTY` | | | Disable pseudo-TTY allocation (default: auto-detected). |
|
||||
| `--no-deps` | | | Don't start linked services. |
|
||||
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host. |
|
||||
| `--quiet-pull` | | | Pull without printing progress information. |
|
||||
| `--remove-orphans` | | | Remove containers for services not defined in the Compose file. |
|
||||
| `--rm` | | | Automatically remove the container when it exits |
|
||||
| `--service-ports` | | | Run command with the service's ports enabled and mapped to the host. |
|
||||
| `--use-aliases` | | | Use the service's network useAliases in the network(s) the container connects to. |
|
||||
| `-u`, `--user` | `string` | | Run as specified username or uid |
|
||||
| `-v`, `--volume` | `stringArray` | | Bind mount a volume. |
|
||||
| `-w`, `--workdir` | `string` | | Working directory inside the container |
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------------|:--------------|:---------|:---------------------------------------------------------------------------------|
|
||||
| `--build` | `bool` | | Build image before starting container |
|
||||
| `--cap-add` | `list` | | Add Linux capabilities |
|
||||
| `--cap-drop` | `list` | | Drop Linux capabilities |
|
||||
| `-d`, `--detach` | `bool` | | Run container in background and print container ID |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--entrypoint` | `string` | | Override the entrypoint of the image |
|
||||
| `-e`, `--env` | `stringArray` | | Set environment variables |
|
||||
| `-i`, `--interactive` | `bool` | `true` | Keep STDIN open even if not attached |
|
||||
| `-l`, `--label` | `stringArray` | | Add or override a label |
|
||||
| `--name` | `string` | | Assign a name to the container |
|
||||
| `-T`, `--no-TTY` | `bool` | `true` | Disable pseudo-TTY allocation (default: auto-detected) |
|
||||
| `--no-deps` | `bool` | | Don't start linked services |
|
||||
| `-p`, `--publish` | `stringArray` | | Publish a container's port(s) to the host |
|
||||
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
|
||||
| `--quiet-pull` | `bool` | | Pull without printing progress information |
|
||||
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
|
||||
| `--rm` | `bool` | | Automatically remove the container when it exits |
|
||||
| `-P`, `--service-ports` | `bool` | | Run command with all service's ports enabled and mapped to the host |
|
||||
| `--use-aliases` | `bool` | | Use the service's network useAliases in the network(s) the container connects to |
|
||||
| `-u`, `--user` | `string` | | Run as specified username or uid |
|
||||
| `-v`, `--volume` | `stringArray` | | Bind mount a volume |
|
||||
| `-w`, `--workdir` | `string` | | Working directory inside the container |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
@@ -5,10 +5,10 @@ Scale services
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-----|:--------|:--------------------------------|
|
||||
| `--dry-run` | | | Execute command in dry run mode |
|
||||
| `--no-deps` | | | Don't start linked services. |
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-------|:--------|:--------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--no-deps` | `bool` | | Don't start linked services |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user