mirror of
https://github.com/docker/compose.git
synced 2025-09-05 17:08:11 +02:00
Compare commits
589 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ad73766bf2 | ||
|
3c1f5a1815 | ||
|
42d1e4c333 | ||
|
6ca8663bda | ||
|
b33ecf65e8 | ||
|
04b8ac5fe4 | ||
|
d09948da41 | ||
|
f1efbb8322 | ||
|
1d52012b82 | ||
|
1d69f4a68c | ||
|
6078b4d99d | ||
|
73e593e69a | ||
|
51499f645b | ||
|
5165b0f814 | ||
|
93dd1a4558 | ||
|
ba3f5664c0 | ||
|
c420bc44c4 | ||
|
60681a824c | ||
|
19ad737ee7 | ||
|
d3a260e533 | ||
|
e75329dce2 | ||
|
1dc0be2c30 | ||
|
3bac9ffd08 | ||
|
f266715dd0 | ||
|
c2cb0aef6b | ||
|
fbc62d111e | ||
|
0d40064ce8 | ||
|
91a6eafa1d | ||
|
f36ee00f71 | ||
|
29ede3ba7d | ||
|
bf6d7bf47e | ||
|
fc66da06db | ||
|
909211dd61 | ||
|
0dc9852c67 | ||
|
a478702236 | ||
|
2c12ad19db | ||
|
038ea8441a | ||
|
9e98e6101e | ||
|
52f04229c0 | ||
|
28895d0322 | ||
|
a926f7d717 | ||
|
fe046915eb | ||
|
adbd61e5d6 | ||
|
e37ac04329 | ||
|
cab2c2a44e | ||
|
1946de598d | ||
|
8e29a138aa | ||
|
3c8da0afee | ||
|
1b12c867c5 | ||
|
1a4fc55fd7 | ||
|
efc939dcee | ||
|
d6e9f79ba6 | ||
|
b4c44a431f | ||
|
fb5a8644c3 | ||
|
95660c5e5a | ||
|
f6ddd6ae88 | ||
|
4ae7066955 | ||
|
fd954f266c | ||
|
d62e21025c | ||
|
6a2d16bd10 | ||
|
4d47da6dc2 | ||
|
8f91793fb5 | ||
|
1d2223fb23 | ||
|
d4f6000712 | ||
|
c50d16cd78 | ||
|
3875e13fad | ||
|
c89f30170d | ||
|
41a9b91887 | ||
|
5fc2b2a71c | ||
|
b1cd40c316 | ||
|
362ab0733f | ||
|
f35d2cfb3b | ||
|
17ba6c7188 | ||
|
1c37f1abb6 | ||
|
485b6200ee | ||
|
8c17a35609 | ||
|
6b9667401a | ||
|
9a1e589ce8 | ||
|
5e147e852e | ||
|
29308cb97e | ||
|
0b0242d0ac | ||
|
5a704004d3 | ||
|
cb95910018 | ||
|
f42226e352 | ||
|
0cc3c7a550 | ||
|
f7ee9c8a0c | ||
|
35efa97b7d | ||
|
9e17a091be | ||
|
4bbc6c609f | ||
|
69f1430a49 | ||
|
7cf7c6414f | ||
|
0e0ed91a39 | ||
|
66524e7728 | ||
|
c626befee1 | ||
|
60ee6adcd2 | ||
|
8faf1eb808 | ||
|
3b0601b671 | ||
|
f42374bb18 | ||
|
b046a5ef72 | ||
|
1570c6c076 | ||
|
674e13fb6d | ||
|
6fa173124a | ||
|
2c69fc3d4d | ||
|
317ebcd3b0 | ||
|
5cf1f0e2a4 | ||
|
6198ed5bd2 | ||
|
00ccddbde8 | ||
|
63b441401e | ||
|
3a7982fe45 | ||
|
5430caa172 | ||
|
ee1b1e0a93 | ||
|
26e46d7cc8 | ||
|
a9e76943f6 | ||
|
b6a0df8d3c | ||
|
5a063b7510 | ||
|
ae49bba9be | ||
|
51acc58453 | ||
|
7c999d7f93 | ||
|
ad750d6143 | ||
|
fe382df507 | ||
|
6501d59efc | ||
|
33a782572f | ||
|
65803ea12e | ||
|
f613379373 | ||
|
3553aa26a6 | ||
|
257ea7b75d | ||
|
d219aa66f4 | ||
|
c9ebfad78e | ||
|
8e57362a0f | ||
|
29630f184e | ||
|
6514c680a5 | ||
|
3394bf031b | ||
|
832a08f579 | ||
|
aadce87b16 | ||
|
b3207c455d | ||
|
769b7391ba | ||
|
149b882ebf | ||
|
c97e40e2b8 | ||
|
22e23bd4dc | ||
|
2dde5faeb8 | ||
|
f7825a56bf | ||
|
4cf075ea0a | ||
|
4f491ffa98 | ||
|
ea1c26d22a | ||
|
9a5fa05ad6 | ||
|
276c229458 | ||
|
eef448dc64 | ||
|
343117233b | ||
|
f599a8cdd2 | ||
|
63b06f5563 | ||
|
1d34661e91 | ||
|
0f9e6ab832 | ||
|
15c9651a3a | ||
|
4893a8b9ad | ||
|
97530790fa | ||
|
213c03f99a | ||
|
ebd7b761f2 | ||
|
ea48480d80 | ||
|
8151b59288 | ||
|
ec49baca56 | ||
|
7b9ad96240 | ||
|
9b67a48c33 | ||
|
0d0e12cc85 | ||
|
92fafccfb2 | ||
|
fee8aee8f0 | ||
|
40f5786e68 | ||
|
61e44da936 | ||
|
0bf7d1ea25 | ||
|
80ace63dfb | ||
|
27e90a3fdf | ||
|
3ca75bdf55 | ||
|
eb3074bbda | ||
|
f4fc010d6b | ||
|
693b9ef078 | ||
|
046879a4a2 | ||
|
7c79b23005 | ||
|
ad4cbee498 | ||
|
60256a875c | ||
|
45bd60c33a | ||
|
cf89fd1aa1 | ||
|
23fef850b9 | ||
|
12b73bea73 | ||
|
2e71440bee | ||
|
d49a68ecbf | ||
|
be83f63f26 | ||
|
9a9227ce64 | ||
|
024f8ebdc5 | ||
|
8c622da20b | ||
|
bbb2b76a14 | ||
|
e45e58b3ec | ||
|
f52af4c868 | ||
|
a54814ff39 | ||
|
a2d7548ca9 | ||
|
8a2cb90a39 | ||
|
cc50ada725 | ||
|
5c74f07991 | ||
|
7e198ee6a3 | ||
|
0566431c64 | ||
|
4f6cc2a330 | ||
|
2352a4a016 | ||
|
1f076a3781 | ||
|
009a239510 | ||
|
3059574288 | ||
|
1229a69384 | ||
|
f2a88e02a0 | ||
|
7f9101845d | ||
|
944e5e67a1 | ||
|
23fc76a540 | ||
|
053d225824 | ||
|
93b597ccec | ||
|
4dcaf94c32 | ||
|
07e7619f4e | ||
|
ed81185c5c | ||
|
22f8a7009f | ||
|
91a0aa0265 | ||
|
7cea455c4d | ||
|
559a51e590 | ||
|
480a556bf0 | ||
|
6263361190 | ||
|
9ee03c3fec | ||
|
4bf18d2325 | ||
|
f0f47a8aa8 | ||
|
d6e3fa6d74 | ||
|
16e83f002d | ||
|
2dbef234dc | ||
|
20f0ffec0b | ||
|
cee6a3c660 | ||
|
fc8c56b407 | ||
|
9c998a934f | ||
|
0403f0d76d | ||
|
91d04a5ca9 | ||
|
d2274ebe6c | ||
|
6e35652182 | ||
|
5bb46035cf | ||
|
f8dae06df8 | ||
|
955e4ed94e | ||
|
60385e6065 | ||
|
f5491328bb | ||
|
f46689a75e | ||
|
8fd0c297f5 | ||
|
f3bbfdae58 | ||
|
322c531a8c | ||
|
bf6b447263 | ||
|
a96c305b25 | ||
|
2d7cd2a999 | ||
|
cbb616ca0c | ||
|
640c7deae0 | ||
|
75b48cfc88 | ||
|
047899c3e6 | ||
|
f91b41875e | ||
|
42cccb1fe8 | ||
|
674af0d66d | ||
|
877d232330 | ||
|
4bba13233b | ||
|
a786e70b0e | ||
|
13cd780f30 | ||
|
8e2f799cd7 | ||
|
2a84dfecfd | ||
|
f6913b0866 | ||
|
d629fffa9e | ||
|
7471e16d85 | ||
|
51907d9f72 | ||
|
a3f88a0a1d | ||
|
c83f1285a8 | ||
|
29e642e232 | ||
|
0c37c10964 | ||
|
43cc2be8ca | ||
|
01e83defc2 | ||
|
846161d447 | ||
|
0bcc629fbe | ||
|
482b622282 | ||
|
ee33143026 | ||
|
cb0b5f6e27 | ||
|
1384853538 | ||
|
096b1e32d3 | ||
|
bf71138df6 | ||
|
a1f673dcf5 | ||
|
02c747a7de | ||
|
88f4f265db | ||
|
e67348222f | ||
|
b543380708 | ||
|
2e75185a07 | ||
|
7bedb5a02c | ||
|
f9cd4d0b1d | ||
|
0badcf3c8d | ||
|
ec49db98d4 | ||
|
e5a353b34d | ||
|
43e456145c | ||
|
75368c7859 | ||
|
6e814eac35 | ||
|
a0d1c3f944 | ||
|
0c5bd16da1 | ||
|
b0badf1eb0 | ||
|
342a2a9e71 | ||
|
7814e5798c | ||
|
42b2e11094 | ||
|
6a8c0988cf | ||
|
9129abe516 | ||
|
f38f3f754c | ||
|
ea07ba8e2a | ||
|
432ae23b0e | ||
|
b6f313b8a5 | ||
|
13618756dc | ||
|
6c1e21572a | ||
|
33e863ac6c | ||
|
f70209cf15 | ||
|
62e832eb50 | ||
|
80e8fda14f | ||
|
375a279785 | ||
|
a766e1669a | ||
|
793c6f1715 | ||
|
8e3e1f7f8b | ||
|
83cafe2838 | ||
|
55b5f233c2 | ||
|
c3a0c35681 | ||
|
8615e9a7c1 | ||
|
b23728941d | ||
|
1a7343bc88 | ||
|
41e6094041 | ||
|
66a47169d5 | ||
|
4c72d3a0e3 | ||
|
59f39b9990 | ||
|
7ab65ba127 | ||
|
d9f05d72d2 | ||
|
7b88c5b0ed | ||
|
eaf9800948 | ||
|
4c2ecb542f | ||
|
bcd000ab40 | ||
|
8092ce9414 | ||
|
97595066e3 | ||
|
508309414f | ||
|
b6c8a2b9fc | ||
|
19571c2c81 | ||
|
0ef7bbcddc | ||
|
66dfa7d181 | ||
|
876ecc48be | ||
|
c7bf302c23 | ||
|
7b3bdbe037 | ||
|
094b48fd74 | ||
|
43c52e2a80 | ||
|
6c1ee1069b | ||
|
e38b729a30 | ||
|
145bb8466d | ||
|
acac184135 | ||
|
3292740c19 | ||
|
cae8e84636 | ||
|
da2eff4ba7 | ||
|
20f780e95a | ||
|
cf2fc2005c | ||
|
d0398a4681 | ||
|
ac40aae4c3 | ||
|
f25fea5e6d | ||
|
b27f56eb19 | ||
|
4e593ed074 | ||
|
d956ff13da | ||
|
5f7c9a2b4b | ||
|
fd0c23a1c9 | ||
|
7aa64ae9cb | ||
|
c23eea9341 | ||
|
036da47950 | ||
|
33172d5e4d | ||
|
3f1a6b72a5 | ||
|
f9a6e6c414 | ||
|
18ef6e592b | ||
|
2884d6df0d | ||
|
4459012a4f | ||
|
6f1f76c0e6 | ||
|
ed72c21871 | ||
|
fa4cfb652e | ||
|
200638b024 | ||
|
a0320f12ef | ||
|
f8a912ab9a | ||
|
c23a7e7281 | ||
|
49575ef499 | ||
|
faa46d374d | ||
|
6ecb8d40ae | ||
|
a6a39422e4 | ||
|
40cd08f318 | ||
|
5e2abb6c26 | ||
|
4db5fcd569 | ||
|
f14c15fa57 | ||
|
8d68ef587f | ||
|
cde9ae5952 | ||
|
806ac91cf6 | ||
|
1c073c0a04 | ||
|
840288895e | ||
|
4b70ff0ccd | ||
|
23351ece81 | ||
|
7c7407672a | ||
|
25cfa66a91 | ||
|
f160333e9e | ||
|
d04b3f48e4 | ||
|
ed10804e0f | ||
|
52578c0998 | ||
|
bd2b49a1cf | ||
|
433a60e122 | ||
|
489fe9cf02 | ||
|
ef1931c8de | ||
|
9be7a3c9a1 | ||
|
666996bee2 | ||
|
083f676214 | ||
|
e81de103db | ||
|
fa39503469 | ||
|
a351585024 | ||
|
b6db1380ec | ||
|
2ebb475433 | ||
|
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 |
2
.github/CODEOWNERS
vendored
Normal file
2
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# global rules
|
||||
* @docker/compose-maintainers
|
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
6
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -12,6 +12,12 @@ body:
|
||||
Include both the current behavior (what you are seeing) as well as what you expected to happen.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
[Docker Swarm](https://www.mirantis.com/software/swarm/) uses a distinct compose file parser and
|
||||
as such doesn't support some of the recent features of Docker Compose. Please contact Mirantis
|
||||
if you need assistance with compose file support in Docker Swarm.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps To Reproduce
|
||||
|
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.
|
||||
|
110
.github/workflows/ci.yml
vendored
110
.github/workflows/ci.yml
vendored
@ -56,7 +56,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Run
|
||||
run: |
|
||||
@ -71,29 +71,47 @@ jobs:
|
||||
matrix:
|
||||
platform: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
||||
steps:
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
platform=${MATRIX_PLATFORM}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
env:
|
||||
MATRIX_PLATFORM: ${{ matrix.platform }}
|
||||
-
|
||||
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@v6
|
||||
with:
|
||||
source: .
|
||||
targets: release
|
||||
provenance: mode=max
|
||||
sbom: true
|
||||
set: |
|
||||
*.platform=${{ matrix.platform }}
|
||||
*.cache-from=type=gha,scope=binary-${{ env.PLATFORM_PAIR }}
|
||||
*.cache-to=type=gha,scope=binary-${{ env.PLATFORM_PAIR }},mode=max
|
||||
-
|
||||
name: Rename provenance and sbom
|
||||
working-directory: ./bin/release
|
||||
run: |
|
||||
binname=$(find . -name 'docker-compose-*')
|
||||
filename=$(basename "$binname" | sed -E 's/\.exe$//')
|
||||
mv "provenance.json" "${filename}.provenance.json"
|
||||
mv "sbom-binary.spdx.json" "${filename}.sbom.json"
|
||||
find . -name 'sbom*.json' -exec rm {} \;
|
||||
-
|
||||
name: List artifacts
|
||||
run: |
|
||||
tree -nh ./bin/release
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
@ -105,15 +123,12 @@ jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
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@v6
|
||||
with:
|
||||
targets: test
|
||||
set: |
|
||||
@ -141,19 +156,19 @@ jobs:
|
||||
- plugin
|
||||
- standalone
|
||||
engine:
|
||||
- 24.0.9
|
||||
- 25.0.5
|
||||
- 26.1.4
|
||||
- 27.0.3
|
||||
- 26
|
||||
- 27
|
||||
- 28
|
||||
steps:
|
||||
- name: Prepare
|
||||
run: |
|
||||
mode=${{ matrix.mode }}
|
||||
engine=${{ matrix.engine }}
|
||||
echo "MODE_ENGINE_PAIR=${mode}-${engine}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Checkout
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Docker ${{ matrix.engine }}
|
||||
run: |
|
||||
sudo systemctl stop docker.service
|
||||
@ -161,22 +176,32 @@ jobs:
|
||||
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
|
||||
-
|
||||
name: Set up Go
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Set up Docker Model
|
||||
run: |
|
||||
sudo apt-get install docker-model-plugin
|
||||
docker model version
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
check-latest: true
|
||||
cache: true
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v2
|
||||
|
||||
- name: Build example provider
|
||||
run: make example-provider
|
||||
|
||||
- name: Build
|
||||
uses: docker/bake-action@v6
|
||||
with:
|
||||
source: .
|
||||
targets: binary-with-coverage
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=binary-linux-amd64
|
||||
@ -184,37 +209,37 @@ jobs:
|
||||
*.cache-to=type=gha,scope=binary-e2e-${{ matrix.mode }},mode=max
|
||||
env:
|
||||
BUILD_TAGS: e2e
|
||||
-
|
||||
name: Setup tmate session
|
||||
|
||||
- name: Setup tmate session
|
||||
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.debug_enabled }}
|
||||
uses: mxschmitt/action-tmate@8b4e4ac71822ed7e0ad5fb3d1c33483e9e8fb270 # v3.11
|
||||
with:
|
||||
limit-access-to-actor: true
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Test plugin mode
|
||||
|
||||
- name: Test plugin mode
|
||||
if: ${{ matrix.mode == 'plugin' }}
|
||||
run: |
|
||||
rm -rf ./bin/coverage/e2e
|
||||
mkdir -p ./bin/coverage/e2e
|
||||
make e2e-compose GOCOVERDIR=bin/coverage/e2e TEST_FLAGS="-v"
|
||||
-
|
||||
name: Gather coverage data
|
||||
|
||||
- name: Gather coverage data
|
||||
if: ${{ matrix.mode == 'plugin' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-data-e2e-${{ env.MODE_ENGINE_PAIR }}
|
||||
path: bin/coverage/e2e/
|
||||
if-no-files-found: error
|
||||
-
|
||||
name: Test standalone mode
|
||||
|
||||
- name: Test standalone mode
|
||||
if: ${{ matrix.mode == 'standalone' }}
|
||||
run: |
|
||||
rm -f /usr/local/bin/docker-compose
|
||||
cp bin/build/docker-compose /usr/local/bin
|
||||
make e2e-compose-standalone
|
||||
-
|
||||
name: e2e Test Summary
|
||||
|
||||
- name: e2e Test Summary
|
||||
uses: test-summary/action@v2
|
||||
with:
|
||||
paths: /tmp/report/report.xml
|
||||
@ -284,10 +309,11 @@ jobs:
|
||||
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
|
||||
mv $RUNNER_TEMP/checksums.txt .
|
||||
cat checksums.txt | while read sum file; do echo "$sum $file" > ${file#\*}.sha256; done
|
||||
-
|
||||
name: License
|
||||
run: cp packaging/* ./bin/release/
|
||||
cat checksums.txt | while read sum file; do
|
||||
if [[ "${file#\*}" == docker-compose-* && "${file#\*}" != *.provenance.json && "${file#\*}" != *.sbom.json ]]; then
|
||||
echo "$sum $file" > ${file#\*}.sha256
|
||||
fi
|
||||
done
|
||||
-
|
||||
name: List artifacts
|
||||
run: |
|
||||
|
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@v5
|
||||
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}}"
|
11
.github/workflows/docs-upstream.yml
vendored
11
.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
|
||||
@ -35,7 +44,7 @@ jobs:
|
||||
retention-days: 1
|
||||
|
||||
validate:
|
||||
uses: docker/docs/.github/workflows/validate-upstream.yml@919a9b9104a34a40b30d116529bcce589a544d1c # pin for artifact v4 support: https://github.com/docker/docs/pull/19220
|
||||
uses: docker/docs/.github/workflows/validate-upstream.yml@main
|
||||
needs:
|
||||
- docs-yaml
|
||||
with:
|
||||
|
48
.github/workflows/merge.yml
vendored
48
.github/workflows/merge.yml
vendored
@ -79,19 +79,35 @@ jobs:
|
||||
outputs:
|
||||
digest: ${{ fromJSON(steps.bake.outputs.metadata).image-cross['containerimage.digest'] }}
|
||||
steps:
|
||||
-
|
||||
name: Free disk space
|
||||
uses: jlumbroso/free-disk-space@54081f138730dfa15788a46383842cd2f914a1be # v1.3.1
|
||||
with:
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
swap-storage: true
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERPUBLICBOT_USERNAME }}
|
||||
password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }}
|
||||
-
|
||||
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 }}
|
||||
@ -99,28 +115,22 @@ jobs:
|
||||
type=ref,event=tag
|
||||
type=edge
|
||||
bake-target: meta-helper
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERPUBLICBOT_USERNAME }}
|
||||
password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }}
|
||||
-
|
||||
name: Build and push image
|
||||
uses: docker/bake-action@v2
|
||||
uses: docker/bake-action@v6
|
||||
id: bake
|
||||
with:
|
||||
source: .
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
${{ steps.meta.outputs.bake-file }}
|
||||
targets: image-cross
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
sbom: true
|
||||
provenance: mode=max
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=bin-image
|
||||
*.cache-to=type=gha,scope=bin-image,mode=max
|
||||
*.attest=type=sbom
|
||||
*.attest=type=provenance,mode=max,builder-id=https://github.com/${{ env.GITHUB_REPOSITORY }}/actions/runs/${{ env.GITHUB_RUN_ID }}
|
||||
|
||||
desktop-edge-test:
|
||||
runs-on: ubuntu-latest
|
||||
@ -129,14 +139,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: |
|
||||
|
23
.github/workflows/scorecards.yml
vendored
23
.github/workflows/scorecards.yml
vendored
@ -7,9 +7,6 @@ on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
|
||||
# Declare default permissions as read only.
|
||||
permissions: read-all
|
||||
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecards analysis
|
||||
@ -19,15 +16,27 @@ jobs:
|
||||
security-events: write
|
||||
# Used to receive a badge.
|
||||
id-token: write
|
||||
# read permissions to all the other objects
|
||||
actions: read
|
||||
attestations: read
|
||||
checks: read
|
||||
contents: read
|
||||
deployments: read
|
||||
issues: read
|
||||
discussions: read
|
||||
packages: read
|
||||
pages: read
|
||||
pull-requests: read
|
||||
statuses: read
|
||||
|
||||
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 +50,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 +58,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
|
||||
|
10
.github/workflows/stale.yml
vendored
10
.github/workflows/stale.yml
vendored
@ -1,4 +1,14 @@
|
||||
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
|
||||
|
120
.golangci.yml
120
.golangci.yml
@ -1,73 +1,89 @@
|
||||
version: "2"
|
||||
run:
|
||||
concurrency: 2
|
||||
timeout: 10m
|
||||
linters:
|
||||
enable-all: false
|
||||
disable-all: true
|
||||
default: none
|
||||
enable:
|
||||
- copyloopvar
|
||||
- depguard
|
||||
- errcheck
|
||||
- errorlint
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- gofmt
|
||||
- goimports
|
||||
- gomodguard
|
||||
- revive
|
||||
- gosimple
|
||||
- govet
|
||||
- ineffassign
|
||||
- lll
|
||||
- misspell
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- revive
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- testifylint
|
||||
- unconvert
|
||||
- unparam
|
||||
- unused
|
||||
linters-settings:
|
||||
revive:
|
||||
rules:
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
depguard:
|
||||
rules:
|
||||
all:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: 'io/ioutil package has been deprecated'
|
||||
- pkg: gopkg.in/yaml.v2
|
||||
desc: 'compose-go uses yaml.v3'
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
- fmt
|
||||
versions:
|
||||
- github.com/distribution/distribution:
|
||||
reason: "use distribution/reference"
|
||||
- gotest.tools:
|
||||
version: "< 3.0.0"
|
||||
reason: "deprecated, pre-modules version"
|
||||
gocritic:
|
||||
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks.
|
||||
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- opinionated
|
||||
- style
|
||||
disabled-checks:
|
||||
- paramTypeCombine
|
||||
- unnamedResult
|
||||
- whyNoLint
|
||||
gocyclo:
|
||||
min-complexity: 16
|
||||
lll:
|
||||
line-length: 200
|
||||
settings:
|
||||
depguard:
|
||||
rules:
|
||||
all:
|
||||
deny:
|
||||
- pkg: io/ioutil
|
||||
desc: io/ioutil package has been deprecated
|
||||
- pkg: github.com/docker/docker/errdefs
|
||||
desc: use github.com/containerd/errdefs instead.
|
||||
- pkg: golang.org/x/exp/maps
|
||||
desc: use stdlib maps package
|
||||
- pkg: golang.org/x/exp/slices
|
||||
desc: use stdlib slices package
|
||||
- pkg: gopkg.in/yaml.v2
|
||||
desc: compose-go uses yaml.v3
|
||||
gocritic:
|
||||
disabled-checks:
|
||||
- paramTypeCombine
|
||||
- unnamedResult
|
||||
- whyNoLint
|
||||
enabled-tags:
|
||||
- diagnostic
|
||||
- opinionated
|
||||
- style
|
||||
gocyclo:
|
||||
min-complexity: 16
|
||||
gomodguard:
|
||||
blocked:
|
||||
modules:
|
||||
- github.com/pkg/errors:
|
||||
recommendations:
|
||||
- errors
|
||||
- fmt
|
||||
versions:
|
||||
- github.com/distribution/distribution:
|
||||
reason: use distribution/reference
|
||||
- gotest.tools:
|
||||
version: < 3.0.0
|
||||
reason: deprecated, pre-modules version
|
||||
lll:
|
||||
line-length: 200
|
||||
revive:
|
||||
rules:
|
||||
- name: package-comments
|
||||
disabled: true
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
issues:
|
||||
# golangci hides some golint warnings (the warning about exported things
|
||||
# withtout documentation for example), this will make it show them anyway.
|
||||
exclude-use-default: false
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- third_party$
|
||||
- builtin$
|
||||
- examples$
|
||||
|
11
BUILDING.md
11
BUILDING.md
@ -2,14 +2,17 @@
|
||||
### Prerequisites
|
||||
|
||||
* Windows:
|
||||
* [Docker Desktop](https://hub.docker.com/editions/community/docker-ce-desktop-windows)
|
||||
* [Docker Desktop](https://docs.docker.com/desktop/setup/install/windows-install/)
|
||||
* make
|
||||
* go (see [go.mod](go.mod) for minimum version)
|
||||
* macOS:
|
||||
* [Docker Desktop](https://hub.docker.com/editions/community/docker-ce-desktop-mac)
|
||||
* [Docker Desktop](https://docs.docker.com/desktop/setup/install/mac-install/)
|
||||
* make
|
||||
* go (see [go.mod](go.mod) for minimum version)
|
||||
* Linux:
|
||||
* [Docker 20.10 or later](https://docs.docker.com/engine/install/)
|
||||
* make
|
||||
* go (see [go.mod](go.mod) for minimum version)
|
||||
|
||||
### Building the CLI
|
||||
|
||||
@ -49,7 +52,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 +88,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
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
Want to hack on Docker? Awesome! We have a contributor's guide that explains
|
||||
[setting up a Docker development environment and the contribution
|
||||
process](https://docs.docker.com/contribute/overview/).
|
||||
process](https://docs.docker.com/contribute/).
|
||||
|
||||
This page contains information about reporting issues as well as some tips and
|
||||
guidelines useful to experienced open source contributors. Finally, make sure
|
||||
@ -95,7 +95,7 @@ don't get discouraged!
|
||||
<tr>
|
||||
<td>Community Slack</td>
|
||||
<td>
|
||||
The Docker Community has a dedicated Slack chat to discuss features and issues. You can sign-up <a href="https://www.docker.com/docker-community" target="_blank">with this link</a>.
|
||||
The Docker Community has a dedicated Slack chat to discuss features and issues. You can sign-up <a href="https://www.docker.com/community/" target="_blank">with this link</a>.
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -118,7 +118,7 @@ don't get discouraged!
|
||||
<td>Stack Overflow</td>
|
||||
<td>
|
||||
Stack Overflow has over 17000 Docker questions listed. We regularly
|
||||
monitor <a href="https://stackoverflow.com/search?tab=newest&q=docker" target="_blank">Docker questions</a>
|
||||
monitor <a href="https://stackoverflow.com/questions/tagged/docker" target="_blank">Docker questions</a>
|
||||
and so do many other knowledgeable Docker users.
|
||||
</td>
|
||||
</tr>
|
||||
@ -200,7 +200,7 @@ For more details, see the [MAINTAINERS](MAINTAINERS) page.
|
||||
The sign-off is a simple line at the end of the explanation for the patch. Your
|
||||
signature certifies that you wrote the patch or otherwise have the right to pass
|
||||
it on as an open-source patch. The rules are pretty simple: if you can certify
|
||||
the below (from [developercertificate.org](http://developercertificate.org/)):
|
||||
the below (from [developercertificate.org](https://developercertificate.org/)):
|
||||
|
||||
```
|
||||
Developer Certificate of Origin
|
||||
@ -252,7 +252,7 @@ commit automatically with `git commit -s`.
|
||||
### How can I become a maintainer?
|
||||
|
||||
The procedures for adding new maintainers are explained in the global
|
||||
[MAINTAINERS](https://github.com/docker/opensource/blob/master/MAINTAINERS)
|
||||
[MAINTAINERS](https://github.com/docker/opensource/blob/main/MAINTAINERS)
|
||||
file in the
|
||||
[https://github.com/docker/opensource/](https://github.com/docker/opensource/)
|
||||
repository.
|
||||
@ -311,8 +311,8 @@ The rules:
|
||||
2. All code should pass the default levels of
|
||||
[`golint`](https://github.com/golang/lint).
|
||||
3. All code should follow the guidelines covered in [Effective
|
||||
Go](http://golang.org/doc/effective_go.html) and [Go Code Review
|
||||
Comments](https://github.com/golang/go/wiki/CodeReviewComments).
|
||||
Go](https://go.dev/doc/effective_go) and [Go Code Review
|
||||
Comments](https://go.dev/wiki/CodeReviewComments).
|
||||
4. Include code comments. Tell us the why, the history and the context.
|
||||
5. Document _all_ declarations and methods, even private ones. Declare
|
||||
expectations, caveats and anything else that may be important. If a type
|
||||
@ -334,6 +334,6 @@ The rules:
|
||||
guidelines. Since you've read all the rules, you now know that.
|
||||
|
||||
If you are having trouble getting into the mood of idiomatic Go, we recommend
|
||||
reading through [Effective Go](https://golang.org/doc/effective_go.html). The
|
||||
[Go Blog](https://blog.golang.org) is also a great resource. Drinking the
|
||||
reading through [Effective Go](https://go.dev/doc/effective_go). The
|
||||
[Go Blog](https://go.dev/blog/) is also a great resource. Drinking the
|
||||
kool-aid is a lot easier than going thirsty.
|
||||
|
@ -15,9 +15,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
ARG GO_VERSION=1.21.11
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG GOLANGCI_LINT_VERSION=v1.55.2
|
||||
ARG GO_VERSION=1.23.12
|
||||
ARG XX_VERSION=1.6.1
|
||||
ARG GOLANGCI_LINT_VERSION=v2.0.2
|
||||
ARG ADDLICENSE_VERSION=v1.0.0
|
||||
|
||||
ARG BUILD_TAGS="e2e"
|
||||
|
@ -23,6 +23,7 @@
|
||||
|
||||
people = [
|
||||
"glours",
|
||||
"jhrotko",
|
||||
"milas",
|
||||
"ndeloof",
|
||||
"nicksieger",
|
||||
@ -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
@ -74,7 +74,7 @@ install: binary
|
||||
install $(or $(DESTDIR),./bin/build)/docker-compose ~/.docker/cli-plugins/docker-compose
|
||||
|
||||
.PHONY: e2e-compose
|
||||
e2e-compose: ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
e2e-compose: example-provider ## Run end to end local tests in plugin mode. Set E2E_TEST=TestName to run a single test
|
||||
go run gotest.tools/gotestsum@latest --format testname --junitfile "/tmp/report/report.xml" -- -v $(TEST_FLAGS) -count=1 ./pkg/e2e
|
||||
|
||||
.PHONY: e2e-compose-standalone
|
||||
@ -87,6 +87,10 @@ build-and-e2e-compose: build e2e-compose ## Compile the compose cli-plugin and r
|
||||
.PHONY: build-and-e2e-compose-standalone
|
||||
build-and-e2e-compose-standalone: build e2e-compose-standalone ## Compile the compose cli-plugin and run End to end local tests in standalone mode. Set E2E_TEST=TestName to run a single test
|
||||
|
||||
.PHONY: example-provider
|
||||
example-provider: ## build example provider for e2e tests
|
||||
go build -o bin/build/example-provider docs/examples/provider.go
|
||||
|
||||
.PHONY: mocks
|
||||
mocks:
|
||||
mockgen --version >/dev/null 2>&1 || go install go.uber.org/mock/mockgen@v0.4.0
|
||||
@ -116,6 +120,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))
|
||||
|
10
README.md
10
README.md
@ -8,7 +8,7 @@
|
||||
- [Legacy](#legacy)
|
||||
# Docker Compose v2
|
||||
|
||||
[](https://github.com/docker/compose/releases/latest)
|
||||
[](https://github.com/docker/compose/releases/latest)
|
||||
[](https://pkg.go.dev/github.com/docker/compose/v2)
|
||||
[](https://github.com/docker/compose/actions?query=workflow%3Aci)
|
||||
[](https://goreportcard.com/report/github.com/docker/compose/v2)
|
||||
@ -23,12 +23,18 @@ your application are configured.
|
||||
Once you have a Compose file, you can create and start your application with a
|
||||
single command: `docker compose up`.
|
||||
|
||||
> **Note**: About Docker Swarm
|
||||
> Docker Swarm used to rely on the legacy compose file format but did not adopted the compose specification
|
||||
> so is missing some of the recent enhancements in the compose syntax. After
|
||||
> [acquisition by Mirantis](https://www.mirantis.com/software/swarm/) swarm isn't maintained by Docker Inc, and
|
||||
> as such some Docker Compose features aren't accessible to swarm users.
|
||||
|
||||
# Where to get Docker Compose
|
||||
|
||||
### Windows and macOS
|
||||
|
||||
Docker Compose is included in
|
||||
[Docker Desktop](https://www.docker.com/products/docker-desktop)
|
||||
[Docker Desktop](https://www.docker.com/products/docker-desktop/)
|
||||
for Windows and macOS.
|
||||
|
||||
### Linux
|
||||
|
@ -55,8 +55,10 @@ func Setup(cmd *cobra.Command, dockerCli command.Cli, args []string) error {
|
||||
ctx,
|
||||
"cli/"+strings.Join(commandName(cmd), "-"),
|
||||
)
|
||||
cmdSpan.SetAttributes(attribute.StringSlice("cli.args", args))
|
||||
cmdSpan.SetAttributes(attribute.StringSlice("cli.flags", getFlags(cmd.Flags())))
|
||||
cmdSpan.SetAttributes(
|
||||
attribute.StringSlice("cli.flags", getFlags(cmd.Flags())),
|
||||
attribute.Bool("cli.isatty", dockerCli.In().IsTerminal()),
|
||||
)
|
||||
|
||||
cmd.SetContext(ctx)
|
||||
wrapRunE(cmd, cmdSpan, tracingShutdown)
|
||||
@ -115,13 +117,14 @@ func wrapRunE(c *cobra.Command, cmdSpan trace.Span, tracingShutdown tracing.Shut
|
||||
}
|
||||
}
|
||||
|
||||
// commandName returns the path components for a given command.
|
||||
// commandName returns the path components for a given command,
|
||||
// in reverse alphabetical order for consistent usage metrics.
|
||||
//
|
||||
// The root Compose command and anything before (i.e. "docker")
|
||||
// are not included.
|
||||
//
|
||||
// For example:
|
||||
// - docker compose alpha watch -> [alpha, watch]
|
||||
// - docker compose alpha watch -> [watch, alpha]
|
||||
// - docker-compose up -> [up]
|
||||
func commandName(cmd *cobra.Command) []string {
|
||||
var name []string
|
||||
|
@ -20,6 +20,8 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
commands "github.com/docker/compose/v2/cmd/compose"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
@ -60,5 +62,51 @@ func TestGetFlags(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestCommandName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
setupCmd func() *cobra.Command
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "docker compose alpha watch -> [watch, alpha]",
|
||||
setupCmd: func() *cobra.Command {
|
||||
dockerCmd := &cobra.Command{Use: "docker"}
|
||||
composeCmd := &cobra.Command{Use: commands.PluginName}
|
||||
alphaCmd := &cobra.Command{Use: "alpha"}
|
||||
watchCmd := &cobra.Command{Use: "watch"}
|
||||
|
||||
dockerCmd.AddCommand(composeCmd)
|
||||
composeCmd.AddCommand(alphaCmd)
|
||||
alphaCmd.AddCommand(watchCmd)
|
||||
|
||||
return watchCmd
|
||||
},
|
||||
want: []string{"watch", "alpha"},
|
||||
},
|
||||
{
|
||||
name: "docker-compose up -> [up]",
|
||||
setupCmd: func() *cobra.Command {
|
||||
dockerComposeCmd := &cobra.Command{Use: commands.PluginName}
|
||||
upCmd := &cobra.Command{Use: "up"}
|
||||
|
||||
dockerComposeCmd.AddCommand(upCmd)
|
||||
|
||||
return upCmd
|
||||
},
|
||||
want: []string{"up"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := tt.setupCmd()
|
||||
got := commandName(cmd)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("commandName() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ ARGS:
|
||||
command = append([]string{arg}, command...)
|
||||
continue
|
||||
}
|
||||
if len(arg) > 0 && arg[0] != '-' {
|
||||
if arg != "" && arg[0] != '-' {
|
||||
command = append(command, args[i:]...)
|
||||
break
|
||||
}
|
||||
|
@ -17,6 +17,9 @@
|
||||
package compatibility
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
@ -24,9 +27,10 @@ import (
|
||||
|
||||
func Test_convert(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want []string
|
||||
name string
|
||||
args []string
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "compose only",
|
||||
@ -93,11 +97,36 @@ func Test_convert(t *testing.T) {
|
||||
args: []string{"--project-name", "compose", "down", "--remove-orphans"},
|
||||
want: []string{"compose", "--project-name", "compose", "down", "--remove-orphans"},
|
||||
},
|
||||
{
|
||||
name: "completion command",
|
||||
args: []string{"__complete", "up"},
|
||||
want: []string{"__complete", "compose", "up"},
|
||||
},
|
||||
{
|
||||
name: "string flag without argument",
|
||||
args: []string{"--log-level"},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := Convert(tt.args)
|
||||
assert.DeepEqual(t, tt.want, got)
|
||||
if tt.wantErr {
|
||||
if os.Getenv("BE_CRASHER") == "1" {
|
||||
Convert(tt.args)
|
||||
return
|
||||
}
|
||||
cmd := exec.Command(os.Args[0], "-test.run=^"+t.Name()+"$")
|
||||
cmd.Env = append(os.Environ(), "BE_CRASHER=1")
|
||||
err := cmd.Run()
|
||||
var e *exec.ExitError
|
||||
if errors.As(err, &e) && !e.Success() {
|
||||
return
|
||||
}
|
||||
t.Fatalf("process ran with err %v, want exit status 1", err)
|
||||
} else {
|
||||
got := Convert(tt.args)
|
||||
assert.DeepEqual(t, tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ func alphaCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
cmd.AddCommand(
|
||||
vizCommand(p, dockerCli, backend),
|
||||
publishCommand(p, dockerCli, backend),
|
||||
generateCommand(p, backend),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
149
cmd/compose/bridge.go
Normal file
149
cmd/compose/bridge.go
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
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"
|
||||
"io"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/bridge"
|
||||
)
|
||||
|
||||
func bridgeCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "bridge CMD [OPTIONS]",
|
||||
Short: "Convert compose files into another model",
|
||||
TraverseChildren: true,
|
||||
}
|
||||
cmd.AddCommand(
|
||||
convertCommand(p, dockerCli),
|
||||
transformersCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func convertCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
convertOpts := bridge.ConvertOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "convert",
|
||||
Short: "Convert compose files to Kubernetes manifests, Helm charts, or another model",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runConvert(ctx, dockerCli, p, convertOpts)
|
||||
}),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
flags.StringVarP(&convertOpts.Output, "output", "o", "out", "The output directory for the Kubernetes resources")
|
||||
flags.StringArrayVarP(&convertOpts.Transformations, "transformation", "t", nil, "Transformation to apply to compose model (default: docker/compose-bridge-kubernetes)")
|
||||
flags.StringVar(&convertOpts.Templates, "templates", "", "Directory containing transformation templates")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runConvert(ctx context.Context, dockerCli command.Cli, p *ProjectOptions, opts bridge.ConvertOptions) error {
|
||||
project, _, err := p.ToProject(ctx, dockerCli, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return bridge.Convert(ctx, dockerCli, project, opts)
|
||||
}
|
||||
|
||||
func transformersCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "transformations CMD [OPTIONS]",
|
||||
Short: "Manage transformation images",
|
||||
}
|
||||
cmd.AddCommand(
|
||||
listTransformersCommand(dockerCli),
|
||||
createTransformerCommand(dockerCli),
|
||||
)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func listTransformersCommand(dockerCli command.Cli) *cobra.Command {
|
||||
options := lsOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Aliases: []string{"ls"},
|
||||
Short: "List available transformations",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
transformers, err := bridge.ListTransformers(ctx, dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return displayTransformer(dockerCli, transformers, options)
|
||||
}),
|
||||
}
|
||||
cmd.Flags().StringVar(&options.Format, "format", "table", "Format the output. Values: [table | json]")
|
||||
cmd.Flags().BoolVarP(&options.Quiet, "quiet", "q", false, "Only display transformer names")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func displayTransformer(dockerCli command.Cli, transformers []image.Summary, options lsOptions) error {
|
||||
if options.Quiet {
|
||||
for _, t := range transformers {
|
||||
if len(t.RepoTags) > 0 {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), t.RepoTags[0])
|
||||
} else {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), t.ID)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return formatter.Print(transformers, options.Format, dockerCli.Out(),
|
||||
func(w io.Writer) {
|
||||
for _, img := range transformers {
|
||||
id := stringid.TruncateID(img.ID)
|
||||
size := units.HumanSizeWithPrecision(float64(img.Size), 3)
|
||||
repo, tag := "<none>", "<none>"
|
||||
if len(img.RepoTags) > 0 {
|
||||
ref, err := reference.ParseDockerRef(img.RepoTags[0])
|
||||
if err == nil {
|
||||
// ParseDockerRef will reject a local image ID
|
||||
repo = reference.FamiliarName(ref)
|
||||
if tagged, ok := ref.(reference.Tagged); ok {
|
||||
tag = tagged.Tag()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", id, repo, tag, size)
|
||||
}
|
||||
},
|
||||
"IMAGE ID", "REPO", "TAGS", "SIZE")
|
||||
}
|
||||
|
||||
func createTransformerCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts bridge.CreateTransformerOptions
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [OPTION] PATH",
|
||||
Short: "Create a new transformation",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
opts.Dest = args[0]
|
||||
return bridge.CreateTransformer(ctx, dockerCli, opts)
|
||||
}),
|
||||
}
|
||||
cmd.Flags().StringVarP(&opts.From, "from", "f", "", "Existing transformation to copy (default: docker/compose-bridge-kubernetes)")
|
||||
return cmd
|
||||
}
|
@ -27,7 +27,6 @@ import (
|
||||
"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"
|
||||
@ -35,20 +34,23 @@ import (
|
||||
|
||||
type buildOptions struct {
|
||||
*ProjectOptions
|
||||
quiet bool
|
||||
pull bool
|
||||
push bool
|
||||
args []string
|
||||
noCache bool
|
||||
memory cliopts.MemBytes
|
||||
ssh string
|
||||
builder string
|
||||
deps bool
|
||||
quiet bool
|
||||
pull bool
|
||||
push bool
|
||||
args []string
|
||||
noCache bool
|
||||
memory cliopts.MemBytes
|
||||
ssh string
|
||||
builder string
|
||||
deps bool
|
||||
print bool
|
||||
check bool
|
||||
sbom string
|
||||
provenance string
|
||||
}
|
||||
|
||||
func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
|
||||
var SSHKeys []types.SSHKey
|
||||
var err error
|
||||
if opts.ssh != "" {
|
||||
id, path, found := strings.Cut(opts.ssh, "=")
|
||||
if !found && id != "default" {
|
||||
@ -58,9 +60,6 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
|
||||
ID: id,
|
||||
Path: path,
|
||||
})
|
||||
if err != nil {
|
||||
return api.BuildOptions{}, err
|
||||
}
|
||||
}
|
||||
builderName := opts.builder
|
||||
if builderName == "" {
|
||||
@ -71,17 +70,23 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
|
||||
if uiMode == ui.ModeJSON {
|
||||
uiMode = "rawjson"
|
||||
}
|
||||
|
||||
return api.BuildOptions{
|
||||
Pull: opts.pull,
|
||||
Push: opts.push,
|
||||
Progress: uiMode,
|
||||
Args: types.NewMappingWithEquals(opts.args),
|
||||
NoCache: opts.noCache,
|
||||
Quiet: opts.quiet,
|
||||
Services: services,
|
||||
Deps: opts.deps,
|
||||
SSHs: SSHKeys,
|
||||
Builder: builderName,
|
||||
Pull: opts.pull,
|
||||
Push: opts.push,
|
||||
Progress: uiMode,
|
||||
Args: types.NewMappingWithEquals(opts.args),
|
||||
NoCache: opts.noCache,
|
||||
Quiet: opts.quiet,
|
||||
Services: services,
|
||||
Deps: opts.deps,
|
||||
Memory: int64(opts.memory),
|
||||
Print: opts.print,
|
||||
Check: opts.check,
|
||||
SSHs: SSHKeys,
|
||||
Builder: builderName,
|
||||
SBOM: opts.sbom,
|
||||
Provenance: opts.provenance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -116,12 +121,14 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
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.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress the build output")
|
||||
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.StringVar(&opts.provenance, "provenance", "", `Add a provenance attestation`)
|
||||
flags.StringVar(&opts.sbom, "sbom", "", `Add a SBOM attestation`)
|
||||
|
||||
flags.Bool("parallel", true, "Build images in parallel. DEPRECATED")
|
||||
flags.MarkHidden("parallel") //nolint:errcheck
|
||||
@ -133,14 +140,17 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
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.StringVar(&p.Progress, "progress", "", fmt.Sprintf(`Set type of ui output (%s)`, strings.Join(printerModes, ", ")))
|
||||
flags.MarkHidden("progress") //nolint:errcheck
|
||||
flags.BoolVar(&opts.print, "print", false, "Print equivalent bake file")
|
||||
flags.BoolVar(&opts.check, "check", false, "Check build configuration")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error {
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
|
||||
opts.All = true // do not drop resources as build may involve some dependencies by additional_contexts
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -150,10 +160,10 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
|
||||
}
|
||||
|
||||
apiBuildOptions, err := opts.toAPIBuildOptions(services)
|
||||
apiBuildOptions.Attestations = true
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiBuildOptions.Memory = int64(opts.memory)
|
||||
return backend.Build(ctx, project, apiBuildOptions)
|
||||
}
|
||||
|
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)
|
||||
}
|
@ -90,3 +90,13 @@ func completeProfileNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn
|
||||
return values, cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
}
|
||||
|
||||
func completeScaleArgs(cli command.Cli, p *ProjectOptions) cobra.CompletionFunc {
|
||||
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
completions, directive := completeServiceNames(cli, p)(cmd, args, toComplete)
|
||||
for i, completion := range completions {
|
||||
completions[i] = completion + "="
|
||||
}
|
||||
return completions, directive
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
@ -35,18 +36,17 @@ import (
|
||||
composegoutils "github.com/compose-spec/compose-go/v2/utils"
|
||||
"github.com/docker/buildx/util/logutil"
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/metadata"
|
||||
"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"
|
||||
@ -60,7 +60,7 @@ 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"
|
||||
@ -68,8 +68,29 @@ const (
|
||||
ComposeEnvFiles = "COMPOSE_ENV_FILES"
|
||||
// ComposeMenu defines if the navigation menu should be rendered. Can be also set via --menu
|
||||
ComposeMenu = "COMPOSE_MENU"
|
||||
// ComposeProgress defines type of progress output, if --progress isn't used
|
||||
ComposeProgress = "COMPOSE_PROGRESS"
|
||||
)
|
||||
|
||||
// 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, vars map[string]string, lookup func(key string) (string, bool)) error {
|
||||
lines, err := kvfile.ParseFromReader(r, lookup)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse env_file %s: %w", filename, err)
|
||||
}
|
||||
for _, line := range lines {
|
||||
key, value, _ := strings.Cut(line, "=")
|
||||
vars[key] = value
|
||||
}
|
||||
return 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
|
||||
|
||||
@ -99,17 +120,9 @@ func AdaptCmd(fn CobraCommand) func(cmd *cobra.Command, args []string) error {
|
||||
}()
|
||||
|
||||
err := fn(ctx, cmd, args)
|
||||
var composeErr compose.Error
|
||||
if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
|
||||
err = dockercli.StatusError{
|
||||
StatusCode: 130,
|
||||
Status: compose.CanceledStatus,
|
||||
}
|
||||
}
|
||||
if errors.As(err, &composeErr) {
|
||||
err = dockercli.StatusError{
|
||||
StatusCode: composeErr.GetMetricsFailureCategory().ExitCode,
|
||||
Status: err.Error(),
|
||||
}
|
||||
}
|
||||
if ui.Mode == ui.ModeJSON {
|
||||
@ -157,7 +170,7 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF
|
||||
return Adapt(func(ctx context.Context, args []string) error {
|
||||
options := []cli.ProjectOptionsFn{
|
||||
cli.WithResolvedPaths(true),
|
||||
cli.WithDiscardEnvFile,
|
||||
cli.WithoutEnvironmentResolution,
|
||||
}
|
||||
|
||||
project, metrics, err := o.ToProject(ctx, dockerCli, args, options...)
|
||||
@ -167,6 +180,11 @@ func (o *ProjectOptions) WithServices(dockerCli command.Cli, fn ProjectServicesF
|
||||
|
||||
ctx = context.WithValue(ctx, tracing.MetricsKey{}, metrics)
|
||||
|
||||
project, err = project.WithServicesEnvironmentResolved(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fn(ctx, project, args)
|
||||
})
|
||||
}
|
||||
@ -211,7 +229,7 @@ func (o *ProjectOptions) addProjectFlags(f *pflag.FlagSet) {
|
||||
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", string(buildkit.AutoMode), fmt.Sprintf(`Set type of progress output (%s)`, strings.Join(printerModes, ", ")))
|
||||
f.StringVar(&o.Progress, "progress", os.Getenv(ComposeProgress), 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")
|
||||
}
|
||||
@ -227,7 +245,7 @@ func (o *ProjectOptions) projectOrName(ctx context.Context, dockerCli command.Cl
|
||||
name := o.ProjectName
|
||||
var project *types.Project
|
||||
if len(o.ConfigPaths) > 0 || o.ProjectName == "" {
|
||||
p, _, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile)
|
||||
p, _, err := o.ToProject(ctx, dockerCli, services, cli.WithDiscardEnvFile, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
envProjectName := os.Getenv(ComposeProjectName)
|
||||
if envProjectName != "" {
|
||||
@ -285,7 +303,7 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, s
|
||||
|
||||
options, err := o.toProjectOptions(po...)
|
||||
if err != nil {
|
||||
return nil, metrics, compose.WrapComposeError(err)
|
||||
return nil, metrics, err
|
||||
}
|
||||
|
||||
options.WithListeners(func(event string, metadata map[string]any) {
|
||||
@ -317,7 +335,7 @@ func (o *ProjectOptions) ToProject(ctx context.Context, dockerCli command.Cli, s
|
||||
|
||||
project, err := options.LoadProject(ctx)
|
||||
if err != nil {
|
||||
return nil, metrics, compose.WrapComposeError(err)
|
||||
return nil, metrics, err
|
||||
}
|
||||
|
||||
if project.Name == "" {
|
||||
@ -359,17 +377,24 @@ func (o *ProjectOptions) remoteLoaders(dockerCli command.Cli) []loader.ResourceL
|
||||
if o.Offline {
|
||||
return nil
|
||||
}
|
||||
git := remote.NewGitRemoteLoader(o.Offline)
|
||||
git := remote.NewGitRemoteLoader(dockerCli, o.Offline)
|
||||
oci := remote.NewOCIRemoteLoader(dockerCli, o.Offline)
|
||||
return []loader.ResourceLoader{git, oci}
|
||||
}
|
||||
|
||||
func (o *ProjectOptions) toProjectOptions(po ...cli.ProjectOptionsFn) (*cli.ProjectOptions, error) {
|
||||
pwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cli.NewProjectOptions(o.ConfigPaths,
|
||||
append(po,
|
||||
cli.WithWorkingDirectory(o.ProjectDir),
|
||||
// First apply os.Environment, always win
|
||||
cli.WithOsEnv,
|
||||
// set PWD as this variable is not consistently supported on Windows
|
||||
cli.WithEnv([]string{"PWD=" + pwd}),
|
||||
// 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
|
||||
@ -391,7 +416,7 @@ const PluginName = "compose"
|
||||
|
||||
// RunningAsStandalone detects when running as a standalone program
|
||||
func RunningAsStandalone() bool {
|
||||
return len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName && os.Args[1] != PluginName
|
||||
return len(os.Args) < 2 || os.Args[1] != metadata.MetadataSubcommandName && os.Args[1] != PluginName
|
||||
}
|
||||
|
||||
// RootCommand returns the compose command with its child commands
|
||||
@ -431,7 +456,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
}
|
||||
_ = cmd.Help()
|
||||
return dockercli.StatusError{
|
||||
StatusCode: compose.CommandSyntaxFailure.ExitCode,
|
||||
StatusCode: 1,
|
||||
Status: fmt.Sprintf("unknown docker command: %q", "compose "+args[0]),
|
||||
}
|
||||
},
|
||||
@ -482,8 +507,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
}
|
||||
|
||||
switch opts.Progress {
|
||||
case ui.ModeAuto:
|
||||
ui.Mode = ui.ModeAuto
|
||||
case "", ui.ModeAuto:
|
||||
if ansi == "never" {
|
||||
ui.Mode = ui.ModePlain
|
||||
}
|
||||
@ -525,10 +549,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
}
|
||||
|
||||
composeCmd := cmd
|
||||
for {
|
||||
if composeCmd.Name() == PluginName {
|
||||
break
|
||||
}
|
||||
for composeCmd.Name() != PluginName {
|
||||
if !composeCmd.HasParent() {
|
||||
return fmt.Errorf("error parsing command line, expected %q", PluginName)
|
||||
}
|
||||
@ -580,7 +601,7 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
}
|
||||
|
||||
c.AddCommand(
|
||||
upCommand(&opts, dockerCli, backend, experiments),
|
||||
upCommand(&opts, dockerCli, backend),
|
||||
downCommand(&opts, dockerCli, backend),
|
||||
startCommand(&opts, dockerCli, backend),
|
||||
restartCommand(&opts, dockerCli, backend),
|
||||
@ -594,6 +615,8 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
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),
|
||||
@ -610,7 +633,10 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
scaleCommand(&opts, dockerCli, backend),
|
||||
statsCommand(&opts, dockerCli),
|
||||
watchCommand(&opts, dockerCli, backend),
|
||||
publishCommand(&opts, dockerCli, backend),
|
||||
alphaCommand(&opts, dockerCli, backend),
|
||||
bridgeCommand(&opts, dockerCli),
|
||||
volumesCommand(&opts, dockerCli, backend),
|
||||
)
|
||||
|
||||
c.Flags().SetInterspersed(false)
|
||||
@ -635,6 +661,10 @@ func RootCommand(dockerCli command.Cli, backend Backend) *cobra.Command { //noli
|
||||
"profile",
|
||||
completeProfileNames(dockerCli, &opts),
|
||||
)
|
||||
c.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||
"progress",
|
||||
cobra.FixedCompletions(printerModes, cobra.ShellCompDirectiveNoFileComp),
|
||||
)
|
||||
|
||||
c.Flags().StringVar(&ansi, "ansi", "auto", `Control when to print ANSI control characters ("never"|"always"|"auto")`)
|
||||
c.Flags().IntVar(¶llel, "parallel", -1, `Control max parallelism, -1 for unlimited`)
|
||||
|
@ -47,14 +47,18 @@ type configOptions struct {
|
||||
noInterpolate bool
|
||||
noNormalize bool
|
||||
noResolvePath bool
|
||||
noResolveEnv bool
|
||||
services bool
|
||||
volumes bool
|
||||
networks bool
|
||||
models bool
|
||||
profiles bool
|
||||
images bool
|
||||
hash string
|
||||
noConsistency bool
|
||||
variables bool
|
||||
environment bool
|
||||
lockImageDigests bool
|
||||
}
|
||||
|
||||
func (o *configOptions) ToProject(ctx context.Context, dockerCli command.Cli, services []string, po ...cli.ProjectOptionsFn) (*types.Project, error) {
|
||||
@ -84,9 +88,8 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
ProjectOptions: p,
|
||||
}
|
||||
cmd := &cobra.Command{
|
||||
Aliases: []string{"convert"}, // for backward compatibility with Cloud integrations
|
||||
Use: "config [OPTIONS] [SERVICE...]",
|
||||
Short: "Parse, resolve and render compose file in canonical format",
|
||||
Use: "config [OPTIONS] [SERVICE...]",
|
||||
Short: "Parse, resolve and render compose file in canonical format",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.quiet {
|
||||
devnull, err := os.Open(os.DevNull)
|
||||
@ -98,6 +101,9 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
if p.Compatibility {
|
||||
opts.noNormalize = true
|
||||
}
|
||||
if opts.lockImageDigests {
|
||||
opts.resolveImageDigests = true
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
@ -107,6 +113,12 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
if opts.volumes {
|
||||
return runVolumes(ctx, dockerCli, opts)
|
||||
}
|
||||
if opts.networks {
|
||||
return runNetworks(ctx, dockerCli, opts)
|
||||
}
|
||||
if opts.models {
|
||||
return runModels(ctx, dockerCli, opts)
|
||||
}
|
||||
if opts.hash != "" {
|
||||
return runHash(ctx, dockerCli, opts)
|
||||
}
|
||||
@ -123,21 +135,28 @@ func configCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
return runEnvironment(ctx, dockerCli, opts, args)
|
||||
}
|
||||
|
||||
if opts.Format == "" {
|
||||
opts.Format = "yaml"
|
||||
}
|
||||
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.StringVar(&opts.Format, "format", "", "Format the output. Values: [yaml | json]")
|
||||
flags.BoolVar(&opts.resolveImageDigests, "resolve-image-digests", false, "Pin image tags to digests")
|
||||
flags.BoolVar(&opts.lockImageDigests, "lock-image-digests", false, "Produces an override file with image 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.noResolveEnv, "no-env-resolution", false, "Don't resolve service env files")
|
||||
|
||||
flags.BoolVar(&opts.services, "services", false, "Print the service names, one per line.")
|
||||
flags.BoolVar(&opts.volumes, "volumes", false, "Print the volume names, one per line.")
|
||||
flags.BoolVar(&opts.networks, "networks", false, "Print the network names, one per line.")
|
||||
flags.BoolVar(&opts.models, "models", false, "Print the model names, one per line.")
|
||||
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.")
|
||||
@ -190,6 +209,13 @@ func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts confi
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.noResolveEnv {
|
||||
project, err = project.WithServicesEnvironmentResolved(true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.noConsistency {
|
||||
err := project.CheckContainerNameUnicity()
|
||||
if err != nil {
|
||||
@ -197,6 +223,10 @@ func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts confi
|
||||
}
|
||||
}
|
||||
|
||||
if opts.lockImageDigests {
|
||||
project = imagesOnly(project)
|
||||
}
|
||||
|
||||
var content []byte
|
||||
switch opts.Format {
|
||||
case "json":
|
||||
@ -212,6 +242,18 @@ func runConfigInterpolate(ctx context.Context, dockerCli command.Cli, opts confi
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// imagesOnly return project with all attributes removed but service.images
|
||||
func imagesOnly(project *types.Project) *types.Project {
|
||||
digests := types.Services{}
|
||||
for name, config := range project.Services {
|
||||
digests[name] = types.ServiceConfig{
|
||||
Image: config.Image,
|
||||
}
|
||||
}
|
||||
project = &types.Project{Services: digests}
|
||||
return project
|
||||
}
|
||||
|
||||
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)
|
||||
@ -226,6 +268,23 @@ func runConfigNoInterpolate(ctx context.Context, dockerCli command.Cli, opts con
|
||||
}
|
||||
}
|
||||
|
||||
if opts.lockImageDigests {
|
||||
for key, e := range model {
|
||||
if key != "services" {
|
||||
delete(model, key)
|
||||
} else {
|
||||
for _, s := range e.(map[string]any) {
|
||||
service := s.(map[string]any)
|
||||
for key := range service {
|
||||
if key != "image" {
|
||||
delete(service, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return formatModel(model, opts.Format)
|
||||
}
|
||||
|
||||
@ -279,14 +338,31 @@ func formatModel(model map[string]any, format string) (content []byte, err error
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
err = project.ForEachService(project.ServiceNames(), func(serviceName string, _ *types.ServiceConfig) error {
|
||||
fmt.Fprintln(dockerCli.Out(), serviceName)
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), serviceName)
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@ -296,7 +372,31 @@ 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
|
||||
}
|
||||
|
||||
func runNetworks(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
|
||||
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for n := range project.Networks {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), n)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runModels(ctx context.Context, dockerCli command.Cli, opts configOptions) error {
|
||||
project, err := opts.ToProject(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, model := range project.Models {
|
||||
if model.Model != "" {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), model.Model)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -331,11 +431,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", name, hash)
|
||||
_, _ = fmt.Fprintf(dockerCli.Out(), "%s %s\n", name, hash)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -357,7 +456,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
|
||||
}
|
||||
@ -369,7 +468,7 @@ func runConfigImages(ctx context.Context, dockerCli command.Cli, opts configOpti
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
@ -383,7 +482,16 @@ func runVariables(ctx context.Context, dockerCli command.Cli, opts configOptions
|
||||
|
||||
variables := template.ExtractVariables(model, template.DefaultPattern)
|
||||
|
||||
return formatter.Print(variables, "", dockerCli.Out(), func(w io.Writer) {
|
||||
if opts.Format == "yaml" {
|
||||
result, err := yaml.Marshal(variables)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Print(string(result))
|
||||
return nil
|
||||
}
|
||||
|
||||
return formatter.Print(variables, opts.Format, 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)
|
||||
}
|
||||
|
@ -66,9 +66,7 @@ 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.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)")
|
||||
|
||||
|
@ -26,7 +26,9 @@ import (
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
)
|
||||
@ -46,6 +48,7 @@ type createOptions struct {
|
||||
timeout int
|
||||
quietPull bool
|
||||
scale []string
|
||||
AssumeYes bool
|
||||
}
|
||||
|
||||
func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
@ -80,6 +83,15 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
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.StringArrayVar(&opts.scale, "scale", []string{}, "Scale SERVICE to NUM instances. Overrides the `scale` setting in the Compose file if present.")
|
||||
flags.BoolVarP(&opts.AssumeYes, "yes", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
|
||||
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
// assumeYes was introduced by mistake as `--y`
|
||||
if name == "y" {
|
||||
logrus.Warn("--y is deprecated, please use --yes instead")
|
||||
name = "yes"
|
||||
}
|
||||
return pflag.NormalizedName(name)
|
||||
})
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -107,6 +119,7 @@ func runCreate(ctx context.Context, _ command.Cli, backend api.Service, createOp
|
||||
Inherit: !createOpts.noInherit,
|
||||
Timeout: createOpts.GetTimeout(),
|
||||
QuietPull: createOpts.quietPull,
|
||||
AssumeYes: createOpts.AssumeYes,
|
||||
})
|
||||
}
|
||||
|
||||
@ -117,6 +130,9 @@ func (opts createOptions) recreateStrategy() string {
|
||||
if opts.forceRecreate {
|
||||
return api.RecreateForce
|
||||
}
|
||||
if opts.noInherit {
|
||||
return api.RecreateForce
|
||||
}
|
||||
return api.RecreateDiverged
|
||||
}
|
||||
|
||||
@ -192,7 +208,9 @@ func applyScaleOpts(project *types.Project, opts []string) 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)
|
||||
}
|
||||
|
@ -40,7 +40,9 @@ func TestRunCreate(t *testing.T) {
|
||||
)
|
||||
|
||||
createOpts := createOptions{}
|
||||
buildOpts := buildOptions{}
|
||||
buildOpts := buildOptions{
|
||||
ProjectOptions: &ProjectOptions{},
|
||||
}
|
||||
project := sampleProject()
|
||||
err := runCreate(ctx, nil, backend, createOpts, buildOpts, project, nil)
|
||||
require.NoError(t, err)
|
||||
@ -58,7 +60,9 @@ func TestRunCreate_Build(t *testing.T) {
|
||||
createOpts := createOptions{
|
||||
Build: true,
|
||||
}
|
||||
buildOpts := buildOptions{}
|
||||
buildOpts := buildOptions{
|
||||
ProjectOptions: &ProjectOptions{},
|
||||
}
|
||||
project := sampleProject()
|
||||
err := runCreate(ctx, nil, backend, createOpts, buildOpts, project, nil)
|
||||
require.NoError(t, err)
|
||||
|
@ -29,7 +29,9 @@ import (
|
||||
|
||||
type eventsOpts struct {
|
||||
*composeOptions
|
||||
json bool
|
||||
json bool
|
||||
since string
|
||||
until string
|
||||
}
|
||||
|
||||
func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
@ -48,6 +50,8 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&opts.json, "json", false, "Output events as a stream of json objects")
|
||||
cmd.Flags().StringVar(&opts.since, "since", "", "Show all events created since timestamp")
|
||||
cmd.Flags().StringVar(&opts.until, "until", "", "Stream events until this timestamp")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -59,6 +63,8 @@ func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
|
||||
return backend.Events(ctx, name, api.EventsOptions{
|
||||
Services: services,
|
||||
Since: opts.since,
|
||||
Until: opts.until,
|
||||
Consumer: func(event api.Event) error {
|
||||
if opts.json {
|
||||
marshal, err := json.Marshal(map[string]interface{}{
|
||||
@ -72,9 +78,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
|
||||
},
|
||||
|
@ -18,12 +18,16 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"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"
|
||||
"github.com/docker/compose/v2/pkg/compose"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@ -59,7 +63,15 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runExec(ctx, dockerCli, backend, opts)
|
||||
err := runExec(ctx, dockerCli, backend, opts)
|
||||
if err != nil {
|
||||
logrus.Debugf("%v", err)
|
||||
var cliError cli.StatusError
|
||||
if ok := errors.As(err, &cliError); ok {
|
||||
os.Exit(err.(cli.StatusError).StatusCode) //nolint: errorlint
|
||||
}
|
||||
}
|
||||
return err
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
@ -86,7 +98,7 @@ func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, op
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectOptions, err := opts.composeOptions.toProjectOptions()
|
||||
projectOptions, err := opts.composeOptions.toProjectOptions() //nolint:staticcheck
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -109,8 +121,8 @@ func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, op
|
||||
|
||||
exitCode, err := backend.Exec(ctx, projectName, execOpts)
|
||||
if exitCode != 0 {
|
||||
errMsg := ""
|
||||
if err != nil {
|
||||
errMsg := fmt.Sprintf("exit status %d", exitCode)
|
||||
if err != nil && err.Error() != "" {
|
||||
errMsg = err.Error()
|
||||
}
|
||||
return cli.StatusError{StatusCode: exitCode, Status: errMsg}
|
||||
|
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
|
||||
}
|
@ -20,9 +20,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/platforms"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
@ -30,7 +33,6 @@ import (
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
)
|
||||
|
||||
type imageOptions struct {
|
||||
@ -76,23 +78,55 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
if i := strings.IndexRune(img.ID, ':'); i >= 0 {
|
||||
id = id[i+1:]
|
||||
}
|
||||
if !utils.StringContains(ids, id) {
|
||||
if !slices.Contains(ids, id) {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
for _, img := range ids {
|
||||
fmt.Fprintln(dockerCli.Out(), img)
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), img)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if opts.Format == "json" {
|
||||
|
||||
sort.Slice(images, func(i, j int) bool {
|
||||
return images[i].ContainerName < images[j].ContainerName
|
||||
})
|
||||
type img struct {
|
||||
ID string `json:"ID"`
|
||||
ContainerName string `json:"ContainerName"`
|
||||
Repository string `json:"Repository"`
|
||||
Tag string `json:"Tag"`
|
||||
Platform string `json:"Platform"`
|
||||
Size int64 `json:"Size"`
|
||||
LastTagTime time.Time `json:"LastTagTime"`
|
||||
}
|
||||
// Convert map to slice
|
||||
var imageList []img
|
||||
for ctr, i := range images {
|
||||
lastTagTime := i.LastTagTime
|
||||
if lastTagTime.IsZero() {
|
||||
lastTagTime = i.Created
|
||||
}
|
||||
imageList = append(imageList, img{
|
||||
ContainerName: ctr,
|
||||
ID: i.ID,
|
||||
Repository: i.Repository,
|
||||
Tag: i.Tag,
|
||||
Platform: platforms.Format(i.Platform),
|
||||
Size: i.Size,
|
||||
LastTagTime: lastTagTime,
|
||||
})
|
||||
}
|
||||
json, err := formatter.ToJSON(imageList, "", "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = fmt.Fprintln(dockerCli.Out(), json)
|
||||
return err
|
||||
}
|
||||
|
||||
return formatter.Print(images, opts.Format, dockerCli.Out(),
|
||||
func(w io.Writer) {
|
||||
for _, img := range images {
|
||||
for _, container := range slices.Sorted(maps.Keys(images)) {
|
||||
img := images[container]
|
||||
id := stringid.TruncateID(img.ID)
|
||||
size := units.HumanSizeWithPrecision(float64(img.Size), 3)
|
||||
repo := img.Repository
|
||||
@ -103,8 +137,10 @@ func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service,
|
||||
if tag == "" {
|
||||
tag = "<none>"
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n", img.ContainerName, repo, tag, id, size)
|
||||
created := units.HumanDuration(time.Now().UTC().Sub(img.LastTagTime)) + " ago"
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
|
||||
container, repo, tag, platforms.Format(img.Platform), id, size, created)
|
||||
}
|
||||
},
|
||||
"CONTAINER", "REPOSITORY", "TAG", "IMAGE ID", "SIZE")
|
||||
"CONTAINER", "REPOSITORY", "TAG", "PLATFORM", "IMAGE ID", "SIZE", "CREATED")
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ func listCommand(dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
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().BoolVarP(&lsOpts.Quiet, "quiet", "q", false, "Only display project names")
|
||||
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")
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
@ -17,10 +17,22 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"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/compose/v2/pkg/utils"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/internal/tracing"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/prompt"
|
||||
)
|
||||
|
||||
func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
@ -32,7 +44,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) {
|
||||
if len(service.Build.Platforms) > 0 && !slices.Contains(service.Build.Platforms, defaultPlatform) {
|
||||
return fmt.Errorf("service %q build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: %s", name, defaultPlatform)
|
||||
}
|
||||
service.Platform = defaultPlatform
|
||||
@ -40,7 +52,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) {
|
||||
if !slices.Contains(service.Build.Platforms, service.Platform) {
|
||||
return fmt.Errorf("service %q build configuration does not support platform: %s", name, service.Platform)
|
||||
}
|
||||
}
|
||||
@ -72,3 +84,208 @@ func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isRemoteConfig checks if the main compose file is from a remote source (OCI or Git)
|
||||
func isRemoteConfig(dockerCli command.Cli, options buildOptions) bool {
|
||||
if len(options.ConfigPaths) == 0 {
|
||||
return false
|
||||
}
|
||||
remoteLoaders := options.remoteLoaders(dockerCli)
|
||||
for _, loader := range remoteLoaders {
|
||||
if loader.Accept(options.ConfigPaths[0]) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// checksForRemoteStack handles environment variable prompts for remote configurations
|
||||
func checksForRemoteStack(ctx context.Context, dockerCli command.Cli, project *types.Project, options buildOptions, assumeYes bool, cmdEnvs []string) error {
|
||||
if !isRemoteConfig(dockerCli, options) {
|
||||
return nil
|
||||
}
|
||||
if metrics, ok := ctx.Value(tracing.MetricsKey{}).(tracing.Metrics); ok && metrics.CountIncludesRemote > 0 {
|
||||
if err := confirmRemoteIncludes(dockerCli, options, assumeYes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
displayLocationRemoteStack(dockerCli, project, options)
|
||||
return promptForInterpolatedVariables(ctx, dockerCli, options.ProjectOptions, assumeYes, cmdEnvs)
|
||||
}
|
||||
|
||||
// Prepare the values map and collect all variables info
|
||||
type varInfo struct {
|
||||
name string
|
||||
value string
|
||||
source string
|
||||
required bool
|
||||
defaultValue string
|
||||
}
|
||||
|
||||
// promptForInterpolatedVariables displays all variables and their values at once,
|
||||
// then prompts for confirmation
|
||||
func promptForInterpolatedVariables(ctx context.Context, dockerCli command.Cli, projectOptions *ProjectOptions, assumeYes bool, cmdEnvs []string) error {
|
||||
if assumeYes {
|
||||
return nil
|
||||
}
|
||||
|
||||
varsInfo, noVariables, err := extractInterpolationVariablesFromModel(ctx, dockerCli, projectOptions, cmdEnvs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if noVariables {
|
||||
return nil
|
||||
}
|
||||
|
||||
displayInterpolationVariables(dockerCli.Out(), varsInfo)
|
||||
|
||||
// Prompt for confirmation
|
||||
userInput := prompt.NewPrompt(dockerCli.In(), dockerCli.Out())
|
||||
msg := "\nDo you want to proceed with these variables? [Y/n]: "
|
||||
confirmed, err := userInput.Confirm(msg, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !confirmed {
|
||||
return fmt.Errorf("operation cancelled by user")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractInterpolationVariablesFromModel(ctx context.Context, dockerCli command.Cli, projectOptions *ProjectOptions, cmdEnvs []string) ([]varInfo, bool, error) {
|
||||
cmdEnvMap := extractEnvCLIDefined(cmdEnvs)
|
||||
|
||||
// Create a model without interpolation to extract variables
|
||||
opts := configOptions{
|
||||
noInterpolate: true,
|
||||
ProjectOptions: projectOptions,
|
||||
}
|
||||
|
||||
model, err := opts.ToModel(ctx, dockerCli, nil, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
// Extract variables that need interpolation
|
||||
variables := template.ExtractVariables(model, template.DefaultPattern)
|
||||
if len(variables) == 0 {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
var varsInfo []varInfo
|
||||
proposedValues := make(map[string]string)
|
||||
|
||||
for name, variable := range variables {
|
||||
info := varInfo{
|
||||
name: name,
|
||||
required: variable.Required,
|
||||
defaultValue: variable.DefaultValue,
|
||||
}
|
||||
|
||||
// Determine value and source based on priority
|
||||
if value, exists := cmdEnvMap[name]; exists {
|
||||
info.value = value
|
||||
info.source = "command-line"
|
||||
proposedValues[name] = value
|
||||
} else if value, exists := os.LookupEnv(name); exists {
|
||||
info.value = value
|
||||
info.source = "environment"
|
||||
proposedValues[name] = value
|
||||
} else if variable.DefaultValue != "" {
|
||||
info.value = variable.DefaultValue
|
||||
info.source = "compose file"
|
||||
proposedValues[name] = variable.DefaultValue
|
||||
} else {
|
||||
info.value = "<unset>"
|
||||
info.source = "none"
|
||||
}
|
||||
|
||||
varsInfo = append(varsInfo, info)
|
||||
}
|
||||
return varsInfo, false, nil
|
||||
}
|
||||
|
||||
func extractEnvCLIDefined(cmdEnvs []string) map[string]string {
|
||||
// Parse command-line environment variables
|
||||
cmdEnvMap := make(map[string]string)
|
||||
for _, env := range cmdEnvs {
|
||||
parts := strings.SplitN(env, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
cmdEnvMap[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
return cmdEnvMap
|
||||
}
|
||||
|
||||
func displayInterpolationVariables(writer io.Writer, varsInfo []varInfo) {
|
||||
// Display all variables in a table format
|
||||
_, _ = fmt.Fprintln(writer, "\nFound the following variables in configuration:")
|
||||
|
||||
w := tabwriter.NewWriter(writer, 0, 0, 3, ' ', 0)
|
||||
_, _ = fmt.Fprintln(w, "VARIABLE\tVALUE\tSOURCE\tREQUIRED\tDEFAULT")
|
||||
sort.Slice(varsInfo, func(a, b int) bool {
|
||||
return varsInfo[a].name < varsInfo[b].name
|
||||
})
|
||||
for _, info := range varsInfo {
|
||||
required := "no"
|
||||
if info.required {
|
||||
required = "yes"
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
|
||||
info.name,
|
||||
info.value,
|
||||
info.source,
|
||||
required,
|
||||
info.defaultValue,
|
||||
)
|
||||
}
|
||||
_ = w.Flush()
|
||||
}
|
||||
|
||||
func displayLocationRemoteStack(dockerCli command.Cli, project *types.Project, options buildOptions) {
|
||||
mainComposeFile := options.ProjectOptions.ConfigPaths[0] //nolint:staticcheck
|
||||
if ui.Mode != ui.ModeQuiet && ui.Mode != ui.ModeJSON {
|
||||
_, _ = fmt.Fprintf(dockerCli.Out(), "Your compose stack %q is stored in %q\n", mainComposeFile, project.WorkingDir)
|
||||
}
|
||||
}
|
||||
|
||||
func confirmRemoteIncludes(dockerCli command.Cli, options buildOptions, assumeYes bool) error {
|
||||
if assumeYes {
|
||||
return nil
|
||||
}
|
||||
|
||||
var remoteIncludes []string
|
||||
remoteLoaders := options.ProjectOptions.remoteLoaders(dockerCli) //nolint:staticcheck
|
||||
for _, cf := range options.ProjectOptions.ConfigPaths { //nolint:staticcheck
|
||||
for _, loader := range remoteLoaders {
|
||||
if loader.Accept(cf) {
|
||||
remoteIncludes = append(remoteIncludes, cf)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(remoteIncludes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), "\nWarning: This Compose project includes files from remote sources:")
|
||||
for _, include := range remoteIncludes {
|
||||
_, _ = fmt.Fprintf(dockerCli.Out(), " - %s\n", include)
|
||||
}
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), "\nRemote includes could potentially be malicious. Make sure you trust the source.")
|
||||
|
||||
msg := "Do you want to continue? [y/N]: "
|
||||
confirmed, err := prompt.NewPrompt(dockerCli.In(), dockerCli.Out()).Confirm(msg, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !confirmed {
|
||||
return fmt.Errorf("operation cancelled by user")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -17,10 +17,20 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestApplyPlatforms_InferFromRuntime(t *testing.T) {
|
||||
@ -128,3 +138,257 @@ func TestApplyPlatforms_UnsupportedPlatform(t *testing.T) {
|
||||
`service "test" build.platforms does not support value set by DOCKER_DEFAULT_PLATFORM: commodore/64`)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIsRemoteConfig(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cli := mocks.NewMockCli(ctrl)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
configPaths []string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty config paths",
|
||||
configPaths: []string{},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "local file",
|
||||
configPaths: []string{"docker-compose.yaml"},
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "OCI reference",
|
||||
configPaths: []string{"oci://registry.example.com/stack:latest"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "GIT reference",
|
||||
configPaths: []string{"git://github.com/user/repo.git"},
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
opts := buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: tt.configPaths,
|
||||
},
|
||||
}
|
||||
got := isRemoteConfig(cli, opts)
|
||||
require.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisplayLocationRemoteStack(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cli := mocks.NewMockCli(ctrl)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes()
|
||||
|
||||
project := &types.Project{
|
||||
Name: "test-project",
|
||||
WorkingDir: "/tmp/test",
|
||||
}
|
||||
|
||||
options := buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: []string{"oci://registry.example.com/stack:latest"},
|
||||
},
|
||||
}
|
||||
|
||||
displayLocationRemoteStack(cli, project, options)
|
||||
|
||||
output := buf.String()
|
||||
require.Equal(t, output, fmt.Sprintf("Your compose stack %q is stored in %q\n", "oci://registry.example.com/stack:latest", "/tmp/test"))
|
||||
}
|
||||
|
||||
func TestDisplayInterpolationVariables(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
// Create a temporary directory for the test
|
||||
tmpDir, err := os.MkdirTemp("", "compose-test")
|
||||
require.NoError(t, err)
|
||||
defer func() { _ = os.RemoveAll(tmpDir) }()
|
||||
|
||||
// Create a temporary compose file
|
||||
composeContent := `
|
||||
services:
|
||||
app:
|
||||
image: nginx
|
||||
environment:
|
||||
- TEST_VAR=${TEST_VAR:?required} # required with default
|
||||
- API_KEY=${API_KEY:?} # required without default
|
||||
- DEBUG=${DEBUG:-true} # optional with default
|
||||
- UNSET_VAR # optional without default
|
||||
`
|
||||
composePath := filepath.Join(tmpDir, "docker-compose.yml")
|
||||
err = os.WriteFile(composePath, []byte(composeContent), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
cli := mocks.NewMockCli(ctrl)
|
||||
cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes()
|
||||
|
||||
// Create ProjectOptions with the temporary compose file
|
||||
projectOptions := &ProjectOptions{
|
||||
ConfigPaths: []string{composePath},
|
||||
}
|
||||
|
||||
// Set up the context with necessary environment variables
|
||||
ctx := context.Background()
|
||||
_ = os.Setenv("TEST_VAR", "test-value")
|
||||
_ = os.Setenv("API_KEY", "123456")
|
||||
defer func() {
|
||||
_ = os.Unsetenv("TEST_VAR")
|
||||
_ = os.Unsetenv("API_KEY")
|
||||
}()
|
||||
|
||||
// Extract variables from the model
|
||||
info, noVariables, err := extractInterpolationVariablesFromModel(ctx, cli, projectOptions, []string{})
|
||||
require.NoError(t, err)
|
||||
require.False(t, noVariables)
|
||||
|
||||
// Display the variables
|
||||
displayInterpolationVariables(cli.Out(), info)
|
||||
|
||||
// Expected output format with proper spacing
|
||||
expected := "\nFound the following variables in configuration:\n" +
|
||||
"VARIABLE VALUE SOURCE REQUIRED DEFAULT\n" +
|
||||
"API_KEY 123456 environment yes \n" +
|
||||
"DEBUG true compose file no true\n" +
|
||||
"TEST_VAR test-value environment yes \n"
|
||||
|
||||
// Normalize spaces and newlines for comparison
|
||||
normalizeSpaces := func(s string) string {
|
||||
// Replace multiple spaces with a single space
|
||||
s = strings.Join(strings.Fields(strings.TrimSpace(s)), " ")
|
||||
return s
|
||||
}
|
||||
|
||||
actualOutput := buf.String()
|
||||
|
||||
// Compare normalized strings
|
||||
require.Equal(t,
|
||||
normalizeSpaces(expected),
|
||||
normalizeSpaces(actualOutput),
|
||||
"\nExpected:\n%s\nGot:\n%s", expected, actualOutput)
|
||||
}
|
||||
|
||||
func TestConfirmRemoteIncludes(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
cli := mocks.NewMockCli(ctrl)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
opts buildOptions
|
||||
assumeYes bool
|
||||
userInput string
|
||||
wantErr bool
|
||||
errMessage string
|
||||
wantPrompt bool
|
||||
wantOutput string
|
||||
}{
|
||||
{
|
||||
name: "no remote includes",
|
||||
opts: buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: []string{
|
||||
"docker-compose.yaml",
|
||||
"./local/path/compose.yaml",
|
||||
},
|
||||
},
|
||||
},
|
||||
assumeYes: false,
|
||||
wantErr: false,
|
||||
wantPrompt: false,
|
||||
},
|
||||
{
|
||||
name: "assume yes with remote includes",
|
||||
opts: buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: []string{
|
||||
"oci://registry.example.com/stack:latest",
|
||||
"git://github.com/user/repo.git",
|
||||
},
|
||||
},
|
||||
},
|
||||
assumeYes: true,
|
||||
wantErr: false,
|
||||
wantPrompt: false,
|
||||
},
|
||||
{
|
||||
name: "user confirms remote includes",
|
||||
opts: buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: []string{
|
||||
"oci://registry.example.com/stack:latest",
|
||||
"git://github.com/user/repo.git",
|
||||
},
|
||||
},
|
||||
},
|
||||
assumeYes: false,
|
||||
userInput: "y\n",
|
||||
wantErr: false,
|
||||
wantPrompt: true,
|
||||
wantOutput: "\nWarning: This Compose project includes files from remote sources:\n" +
|
||||
" - oci://registry.example.com/stack:latest\n" +
|
||||
" - git://github.com/user/repo.git\n" +
|
||||
"\nRemote includes could potentially be malicious. Make sure you trust the source.\n" +
|
||||
"Do you want to continue? [y/N]: ",
|
||||
},
|
||||
{
|
||||
name: "user rejects remote includes",
|
||||
opts: buildOptions{
|
||||
ProjectOptions: &ProjectOptions{
|
||||
ConfigPaths: []string{
|
||||
"oci://registry.example.com/stack:latest",
|
||||
},
|
||||
},
|
||||
},
|
||||
assumeYes: false,
|
||||
userInput: "n\n",
|
||||
wantErr: true,
|
||||
errMessage: "operation cancelled by user",
|
||||
wantPrompt: true,
|
||||
wantOutput: "\nWarning: This Compose project includes files from remote sources:\n" +
|
||||
" - oci://registry.example.com/stack:latest\n" +
|
||||
"\nRemote includes could potentially be malicious. Make sure you trust the source.\n" +
|
||||
"Do you want to continue? [y/N]: ",
|
||||
},
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes()
|
||||
|
||||
if tt.wantPrompt {
|
||||
inbuf := io.NopCloser(bytes.NewBufferString(tt.userInput))
|
||||
cli.EXPECT().In().Return(streams.NewIn(inbuf)).AnyTimes()
|
||||
}
|
||||
|
||||
err := confirmRemoteIncludes(cli, tt.opts, tt.assumeYes)
|
||||
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
require.Equal(t, tt.errMessage, err.Error())
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
if tt.wantOutput != "" {
|
||||
require.Equal(t, tt.wantOutput, buf.String())
|
||||
}
|
||||
buf.Reset()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -20,12 +20,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
cliformatter "github.com/docker/cli/cli/command/formatter"
|
||||
@ -101,7 +101,7 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv
|
||||
names := project.ServiceNames()
|
||||
if len(services) > 0 {
|
||||
for _, service := range services {
|
||||
if !utils.StringContains(names, service) {
|
||||
if !slices.Contains(names, service) {
|
||||
return fmt.Errorf("no such service: %s", service)
|
||||
}
|
||||
}
|
||||
@ -130,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
|
||||
}
|
||||
@ -139,11 +139,11 @@ func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, serv
|
||||
services := []string{}
|
||||
for _, c := range containers {
|
||||
s := c.Service
|
||||
if !utils.StringContains(services, s) {
|
||||
if !slices.Contains(services, s) {
|
||||
services = append(services, s)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(dockerCli.Out(), strings.Join(services, "\n"))
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), strings.Join(services, "\n"))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
@ -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")
|
||||
}
|
||||
|
@ -18,17 +18,22 @@ package compose
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type publishOptions struct {
|
||||
*ProjectOptions
|
||||
resolveImageDigests bool
|
||||
ociVersion string
|
||||
withEnvironment bool
|
||||
assumeYes bool
|
||||
}
|
||||
|
||||
func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
@ -36,27 +41,44 @@ 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])
|
||||
}),
|
||||
Args: cobra.ExactArgs(1),
|
||||
Args: cli.ExactArgs(1),
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
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)")
|
||||
flags.StringVar(&opts.ociVersion, "oci-version", "", "OCI image/artifact specification version (automatically determined by default)")
|
||||
flags.BoolVar(&opts.withEnvironment, "with-env", false, "Include environment variables in the published OCI artifact")
|
||||
flags.BoolVarP(&opts.assumeYes, "yes", "y", false, `Assume "yes" as answer to all prompts`)
|
||||
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
// assumeYes was introduced by mistake as `--y`
|
||||
if name == "y" {
|
||||
logrus.Warn("--y is deprecated, please use --yes instead")
|
||||
name = "yes"
|
||||
}
|
||||
return pflag.NormalizedName(name)
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts publishOptions, repository string) error {
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, nil)
|
||||
project, metrics, err := opts.ToProject(ctx, dockerCli, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if metrics.CountIncludesLocal > 0 {
|
||||
return errors.New("cannot publish compose file with local includes")
|
||||
}
|
||||
|
||||
return backend.Publish(ctx, project, repository, api.PublishOptions{
|
||||
ResolveImageDigests: opts.resolveImageDigests,
|
||||
OCIVersion: api.OCIVersion(opts.ociVersion),
|
||||
WithEnvironment: opts.withEnvironment,
|
||||
AssumeYes: opts.assumeYes,
|
||||
})
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/morikuni/aec"
|
||||
@ -48,12 +49,15 @@ func pullCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
cmd := &cobra.Command{
|
||||
Use: "pull [OPTIONS] [SERVICE...]",
|
||||
Short: "Pull service images",
|
||||
PreRunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
if opts.noParallel {
|
||||
PreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
if cmd.Flags().Changed("no-parallel") {
|
||||
fmt.Fprint(os.Stderr, aec.Apply("option '--no-parallel' is DEPRECATED and will be ignored.\n", aec.RedF))
|
||||
}
|
||||
if cmd.Flags().Changed("parallel") {
|
||||
fmt.Fprint(os.Stderr, aec.Apply("option '--parallel' is DEPRECATED and will be ignored.\n", aec.RedF))
|
||||
}
|
||||
return nil
|
||||
}),
|
||||
},
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runPull(ctx, dockerCli, backend, opts, args)
|
||||
}),
|
||||
@ -64,7 +68,7 @@ func pullCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
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.noParallel, "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")
|
||||
@ -94,7 +98,7 @@ func (opts pullOptions) apply(project *types.Project, services []string) (*types
|
||||
}
|
||||
|
||||
func runPull(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pullOptions, services []string) error {
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services)
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -19,8 +19,10 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/dotenv"
|
||||
"github.com/compose-spec/compose-go/v2/format"
|
||||
xprogress "github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/sirupsen/logrus"
|
||||
@ -44,6 +46,7 @@ type runOptions struct {
|
||||
Service string
|
||||
Command []string
|
||||
environment []string
|
||||
envFiles []string
|
||||
Detach bool
|
||||
Remove bool
|
||||
noTty bool
|
||||
@ -63,6 +66,8 @@ type runOptions struct {
|
||||
name string
|
||||
noDeps bool
|
||||
ignoreOrphans bool
|
||||
removeOrphans bool
|
||||
quiet bool
|
||||
quietPull bool
|
||||
}
|
||||
|
||||
@ -115,6 +120,29 @@ func (options runOptions) apply(project *types.Project) (*types.Project, error)
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (options runOptions) getEnvironment() (types.Mapping, error) {
|
||||
environment := types.NewMappingWithEquals(options.environment).Resolve(os.LookupEnv).ToMapping()
|
||||
for _, file := range options.envFiles {
|
||||
f, err := os.Open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vars, err := dotenv.ParseWithLookup(f, func(k string) (string, bool) {
|
||||
value, ok := environment[k]
|
||||
return value, ok
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
for k, v := range vars {
|
||||
if _, ok := environment[k]; !ok {
|
||||
environment[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
return environment, nil
|
||||
}
|
||||
|
||||
func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
options := runOptions{
|
||||
composeOptions: &composeOptions{
|
||||
@ -153,10 +181,24 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
options.noTty = !options.tty
|
||||
}
|
||||
}
|
||||
if options.quiet {
|
||||
progress.Mode = progress.ModeQuiet
|
||||
devnull, err := os.Open(os.DevNull)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Stdout = devnull
|
||||
}
|
||||
createOpts.pullChanged = cmd.Flags().Changed("pull")
|
||||
return nil
|
||||
}),
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithDiscardEnvFile)
|
||||
project, _, err := p.ToProject(ctx, dockerCli, []string{options.Service}, cgo.WithResolvedPaths(true), cgo.WithoutEnvironmentResolution)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
project, err = project.WithServicesEnvironmentResolved(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -173,6 +215,7 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&options.Detach, "detach", "d", false, "Run container in background and print container ID")
|
||||
flags.StringArrayVarP(&options.environment, "env", "e", []string{}, "Set environment variables")
|
||||
flags.StringArrayVar(&options.envFiles, "env-from-file", []string{}, "Set environment variables from file")
|
||||
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)")
|
||||
@ -187,9 +230,12 @@ func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
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.BoolVarP(&options.quiet, "quiet", "q", false, "Don't print anything to STDOUT")
|
||||
flags.BoolVar(&buildOpts.quiet, "quiet-build", false, "Suppress progress output from the build process")
|
||||
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.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")
|
||||
@ -221,19 +267,7 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
return err
|
||||
}
|
||||
|
||||
err = progress.Run(ctx, func(ctx context.Context) error {
|
||||
var buildForDeps *api.BuildOptions
|
||||
if !createOpts.noBuild {
|
||||
// allow dependencies needing build to be implicitly selected
|
||||
bo, err := buildOpts.toAPIBuildOptions(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildForDeps = &bo
|
||||
}
|
||||
return startDependencies(ctx, backend, *project, buildForDeps, options)
|
||||
}, dockerCli.Err())
|
||||
if err != nil {
|
||||
if err := checksForRemoteStack(ctx, dockerCli, project, buildOpts, createOpts.AssumeYes, []string{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -248,18 +282,26 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
|
||||
var buildForRun *api.BuildOptions
|
||||
if !createOpts.noBuild {
|
||||
// dependencies have already been started above, so only the service
|
||||
// being run might need to be built at this point
|
||||
bo, err := buildOpts.toAPIBuildOptions([]string{options.Service})
|
||||
bo, err := buildOpts.toAPIBuildOptions(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
buildForRun = &bo
|
||||
}
|
||||
|
||||
environment, err := options.getEnvironment()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// start container and attach to container streams
|
||||
runOpts := api.RunOptions{
|
||||
Build: buildForRun,
|
||||
CreateOptions: api.CreateOptions{
|
||||
Build: buildForRun,
|
||||
RemoveOrphans: options.removeOrphans,
|
||||
IgnoreOrphans: options.ignoreOrphans,
|
||||
QuietPull: options.quietPull,
|
||||
},
|
||||
Name: options.name,
|
||||
Service: options.Service,
|
||||
Command: options.Command,
|
||||
@ -269,15 +311,14 @@ func runRun(ctx context.Context, backend api.Service, project *types.Project, op
|
||||
Interactive: options.interactive,
|
||||
WorkingDir: options.workdir,
|
||||
User: options.user,
|
||||
CapAdd: options.capAdd.GetAll(),
|
||||
CapDrop: options.capDrop.GetAll(),
|
||||
Environment: options.environment,
|
||||
CapAdd: options.capAdd.GetSlice(),
|
||||
CapDrop: options.capDrop.GetSlice(),
|
||||
Environment: environment.Values(),
|
||||
Entrypoint: options.entrypointCmd,
|
||||
Labels: labels,
|
||||
UseNetworkAliases: options.useAliases,
|
||||
NoDeps: options.noDeps,
|
||||
Index: 0,
|
||||
QuietPull: options.quietPull,
|
||||
}
|
||||
|
||||
for name, service := range project.Services {
|
||||
@ -297,33 +338,3 @@ 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, options runOptions) error {
|
||||
dependencies := types.Services{}
|
||||
var requestedService types.ServiceConfig
|
||||
for name, service := range project.Services {
|
||||
if name != options.Service {
|
||||
dependencies[name] = service
|
||||
} else {
|
||||
requestedService = service
|
||||
}
|
||||
}
|
||||
|
||||
project.Services = dependencies
|
||||
project.DisabledServices[options.Service] = requestedService
|
||||
err := backend.Create(ctx, &project, api.CreateOptions{
|
||||
Build: buildOpts,
|
||||
IgnoreOrphans: options.ignoreOrphans,
|
||||
QuietPull: options.quietPull,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(dependencies) > 0 {
|
||||
return backend.Start(ctx, project.Name, api.StartOptions{
|
||||
Project: &project,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -19,14 +19,13 @@ package compose
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -51,7 +50,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
return runScale(ctx, dockerCli, backend, opts, serviceTuples)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
ValidArgsFunction: completeScaleArgs(dockerCli, p),
|
||||
}
|
||||
flags := scaleCmd.Flags()
|
||||
flags.BoolVar(&opts.noDeps, "no-deps", false, "Don't start linked services")
|
||||
@ -60,7 +59,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
func runScale(ctx context.Context, dockerCli command.Cli, backend api.Service, opts scaleOptions, serviceReplicaTuples map[string]int) error {
|
||||
services := maps.Keys(serviceReplicaTuples)
|
||||
services := slices.Sorted(maps.Keys(serviceReplicaTuples))
|
||||
project, _, err := opts.ToProject(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -92,7 +91,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)
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ func statsCommand(p *ProjectOptions, dockerCli command.Cli) *cobra.Command {
|
||||
'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`)
|
||||
Refer to https://docs.docker.com/engine/cli/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
|
||||
|
@ -49,6 +49,11 @@ func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *
|
||||
return topCmd
|
||||
}
|
||||
|
||||
type (
|
||||
topHeader map[string]int // maps a proc title to its output index
|
||||
topEntries map[string]string
|
||||
)
|
||||
|
||||
func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts topOptions, services []string) error {
|
||||
projectName, err := opts.toProjectName(ctx, dockerCli)
|
||||
if err != nil {
|
||||
@ -63,30 +68,76 @@ func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opt
|
||||
return containers[i].Name < containers[j].Name
|
||||
})
|
||||
|
||||
for _, container := range containers {
|
||||
fmt.Fprintf(dockerCli.Out(), "%s\n", container.Name)
|
||||
err := psPrinter(dockerCli.Out(), func(w io.Writer) {
|
||||
for _, proc := range container.Processes {
|
||||
info := []interface{}{}
|
||||
for _, p := range proc {
|
||||
info = append(info, p)
|
||||
}
|
||||
_, _ = fmt.Fprintf(w, strings.Repeat("%s\t", len(info))+"\n", info...)
|
||||
header, entries := collectTop(containers)
|
||||
return topPrint(dockerCli.Out(), header, entries)
|
||||
}
|
||||
|
||||
func collectTop(containers []api.ContainerProcSummary) (topHeader, []topEntries) {
|
||||
// map column name to its header (should keep working if backend.Top returns
|
||||
// varying columns for different containers)
|
||||
header := topHeader{"SERVICE": 0, "#": 1}
|
||||
|
||||
// assume one process per container and grow if needed
|
||||
entries := make([]topEntries, 0, len(containers))
|
||||
|
||||
for _, container := range containers {
|
||||
for _, proc := range container.Processes {
|
||||
entry := topEntries{
|
||||
"SERVICE": container.Service,
|
||||
"#": container.Replica,
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
},
|
||||
container.Titles...)
|
||||
if err != nil {
|
||||
return err
|
||||
for i, title := range container.Titles {
|
||||
if _, exists := header[title]; !exists {
|
||||
header[title] = len(header)
|
||||
}
|
||||
entry[title] = proc[i]
|
||||
}
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
// ensure CMD is the right-most column
|
||||
if pos, ok := header["CMD"]; ok {
|
||||
maxPos := pos
|
||||
for h, i := range header {
|
||||
if i > maxPos {
|
||||
maxPos = i
|
||||
}
|
||||
if i > pos {
|
||||
header[h] = i - 1
|
||||
}
|
||||
}
|
||||
header["CMD"] = maxPos
|
||||
}
|
||||
|
||||
return header, entries
|
||||
}
|
||||
|
||||
func psPrinter(out io.Writer, printer func(writer io.Writer), headers ...string) error {
|
||||
w := tabwriter.NewWriter(out, 5, 1, 3, ' ', 0)
|
||||
_, _ = fmt.Fprintln(w, strings.Join(headers, "\t"))
|
||||
printer(w)
|
||||
func topPrint(out io.Writer, headers topHeader, rows []topEntries) error {
|
||||
if len(rows) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(out, 4, 1, 2, ' ', 0)
|
||||
|
||||
// write headers in the order we've encountered them
|
||||
h := make([]string, len(headers))
|
||||
for title, index := range headers {
|
||||
h[index] = title
|
||||
}
|
||||
_, _ = fmt.Fprintln(w, strings.Join(h, "\t"))
|
||||
|
||||
for _, row := range rows {
|
||||
// write proc data in header order
|
||||
r := make([]string, len(headers))
|
||||
for title, index := range headers {
|
||||
if v, ok := row[title]; ok {
|
||||
r[index] = v
|
||||
} else {
|
||||
r[index] = "-"
|
||||
}
|
||||
}
|
||||
_, _ = fmt.Fprintln(w, strings.Join(r, "\t"))
|
||||
}
|
||||
return w.Flush()
|
||||
}
|
||||
|
329
cmd/compose/top_test.go
Normal file
329
cmd/compose/top_test.go
Normal file
@ -0,0 +1,329 @@
|
||||
/*
|
||||
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 compose
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var topTestCases = []struct {
|
||||
name string
|
||||
titles []string
|
||||
procs [][]string
|
||||
|
||||
header topHeader
|
||||
entries []topEntries
|
||||
output string
|
||||
}{
|
||||
{
|
||||
name: "noprocs",
|
||||
titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"},
|
||||
procs: [][]string{},
|
||||
header: topHeader{"SERVICE": 0, "#": 1},
|
||||
entries: []topEntries{},
|
||||
output: "",
|
||||
},
|
||||
{
|
||||
name: "simple",
|
||||
titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"},
|
||||
procs: [][]string{{"root", "1", "1", "0", "12:00", "?", "00:00:01", "/entrypoint"}},
|
||||
header: topHeader{
|
||||
"SERVICE": 0,
|
||||
"#": 1,
|
||||
"UID": 2,
|
||||
"PID": 3,
|
||||
"PPID": 4,
|
||||
"C": 5,
|
||||
"STIME": 6,
|
||||
"TTY": 7,
|
||||
"TIME": 8,
|
||||
"CMD": 9,
|
||||
},
|
||||
entries: []topEntries{
|
||||
{
|
||||
"SERVICE": "simple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:01",
|
||||
"CMD": "/entrypoint",
|
||||
},
|
||||
},
|
||||
output: trim(`
|
||||
SERVICE # UID PID PPID C STIME TTY TIME CMD
|
||||
simple 1 root 1 1 0 12:00 ? 00:00:01 /entrypoint
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "noppid",
|
||||
titles: []string{"UID", "PID", "C", "STIME", "TTY", "TIME", "CMD"},
|
||||
procs: [][]string{{"root", "1", "0", "12:00", "?", "00:00:02", "/entrypoint"}},
|
||||
header: topHeader{
|
||||
"SERVICE": 0,
|
||||
"#": 1,
|
||||
"UID": 2,
|
||||
"PID": 3,
|
||||
"C": 4,
|
||||
"STIME": 5,
|
||||
"TTY": 6,
|
||||
"TIME": 7,
|
||||
"CMD": 8,
|
||||
},
|
||||
entries: []topEntries{
|
||||
{
|
||||
"SERVICE": "noppid",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:02",
|
||||
"CMD": "/entrypoint",
|
||||
},
|
||||
},
|
||||
output: trim(`
|
||||
SERVICE # UID PID C STIME TTY TIME CMD
|
||||
noppid 1 root 1 0 12:00 ? 00:00:02 /entrypoint
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "extra-hdr",
|
||||
titles: []string{"UID", "GID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"},
|
||||
procs: [][]string{{"root", "1", "1", "1", "0", "12:00", "?", "00:00:03", "/entrypoint"}},
|
||||
header: topHeader{
|
||||
"SERVICE": 0,
|
||||
"#": 1,
|
||||
"UID": 2,
|
||||
"GID": 3,
|
||||
"PID": 4,
|
||||
"PPID": 5,
|
||||
"C": 6,
|
||||
"STIME": 7,
|
||||
"TTY": 8,
|
||||
"TIME": 9,
|
||||
"CMD": 10,
|
||||
},
|
||||
entries: []topEntries{
|
||||
{
|
||||
"SERVICE": "extra-hdr",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"GID": "1",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:03",
|
||||
"CMD": "/entrypoint",
|
||||
},
|
||||
},
|
||||
output: trim(`
|
||||
SERVICE # UID GID PID PPID C STIME TTY TIME CMD
|
||||
extra-hdr 1 root 1 1 1 0 12:00 ? 00:00:03 /entrypoint
|
||||
`),
|
||||
},
|
||||
{
|
||||
name: "multiple",
|
||||
titles: []string{"UID", "PID", "PPID", "C", "STIME", "TTY", "TIME", "CMD"},
|
||||
procs: [][]string{
|
||||
{"root", "1", "1", "0", "12:00", "?", "00:00:04", "/entrypoint"},
|
||||
{"root", "123", "1", "0", "12:00", "?", "00:00:42", "sleep infinity"},
|
||||
},
|
||||
header: topHeader{
|
||||
"SERVICE": 0,
|
||||
"#": 1,
|
||||
"UID": 2,
|
||||
"PID": 3,
|
||||
"PPID": 4,
|
||||
"C": 5,
|
||||
"STIME": 6,
|
||||
"TTY": 7,
|
||||
"TIME": 8,
|
||||
"CMD": 9,
|
||||
},
|
||||
entries: []topEntries{
|
||||
{
|
||||
"SERVICE": "multiple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:04",
|
||||
"CMD": "/entrypoint",
|
||||
},
|
||||
{
|
||||
"SERVICE": "multiple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "123",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:42",
|
||||
"CMD": "sleep infinity",
|
||||
},
|
||||
},
|
||||
output: trim(`
|
||||
SERVICE # UID PID PPID C STIME TTY TIME CMD
|
||||
multiple 1 root 1 1 0 12:00 ? 00:00:04 /entrypoint
|
||||
multiple 1 root 123 1 0 12:00 ? 00:00:42 sleep infinity
|
||||
`),
|
||||
},
|
||||
}
|
||||
|
||||
// TestRunTopCore only tests the core functionality of runTop: formatting
|
||||
// and printing of the output of (api.Service).Top().
|
||||
func TestRunTopCore(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
all := []api.ContainerProcSummary{}
|
||||
|
||||
for _, tc := range topTestCases {
|
||||
summary := api.ContainerProcSummary{
|
||||
Name: "not used",
|
||||
Titles: tc.titles,
|
||||
Processes: tc.procs,
|
||||
Service: tc.name,
|
||||
Replica: "1",
|
||||
}
|
||||
all = append(all, summary)
|
||||
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
header, entries := collectTop([]api.ContainerProcSummary{summary})
|
||||
assert.Equal(t, tc.header, header)
|
||||
assert.Equal(t, tc.entries, entries)
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := topPrint(&buf, header, entries)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tc.output, buf.String())
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("all", func(t *testing.T) {
|
||||
header, entries := collectTop(all)
|
||||
assert.Equal(t, topHeader{
|
||||
"SERVICE": 0,
|
||||
"#": 1,
|
||||
"UID": 2,
|
||||
"PID": 3,
|
||||
"PPID": 4,
|
||||
"C": 5,
|
||||
"STIME": 6,
|
||||
"TTY": 7,
|
||||
"TIME": 8,
|
||||
"GID": 9,
|
||||
"CMD": 10,
|
||||
}, header)
|
||||
assert.Equal(t, []topEntries{
|
||||
{
|
||||
"SERVICE": "simple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:01",
|
||||
"CMD": "/entrypoint",
|
||||
}, {
|
||||
"SERVICE": "noppid",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:02",
|
||||
"CMD": "/entrypoint",
|
||||
}, {
|
||||
"SERVICE": "extra-hdr",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"GID": "1",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:03",
|
||||
"CMD": "/entrypoint",
|
||||
}, {
|
||||
"SERVICE": "multiple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "1",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:04",
|
||||
"CMD": "/entrypoint",
|
||||
}, {
|
||||
"SERVICE": "multiple",
|
||||
"#": "1",
|
||||
"UID": "root",
|
||||
"PID": "123",
|
||||
"PPID": "1",
|
||||
"C": "0",
|
||||
"STIME": "12:00",
|
||||
"TTY": "?",
|
||||
"TIME": "00:00:42",
|
||||
"CMD": "sleep infinity",
|
||||
},
|
||||
}, entries)
|
||||
|
||||
var buf bytes.Buffer
|
||||
err := topPrint(&buf, header, entries)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, trim(`
|
||||
SERVICE # UID PID PPID C STIME TTY TIME GID CMD
|
||||
simple 1 root 1 1 0 12:00 ? 00:00:01 - /entrypoint
|
||||
noppid 1 root 1 - 0 12:00 ? 00:00:02 - /entrypoint
|
||||
extra-hdr 1 root 1 1 0 12:00 ? 00:00:03 1 /entrypoint
|
||||
multiple 1 root 1 1 0 12:00 ? 00:00:04 - /entrypoint
|
||||
multiple 1 root 123 1 0 12:00 ? 00:00:42 - sleep infinity
|
||||
`), buf.String())
|
||||
})
|
||||
}
|
||||
|
||||
func trim(s string) string {
|
||||
var out bytes.Buffer
|
||||
for _, line := range strings.Split(strings.TrimSpace(s), "\n") {
|
||||
out.WriteString(strings.TrimSpace(line))
|
||||
out.WriteRune('\n')
|
||||
}
|
||||
return out.String()
|
||||
}
|
@ -26,11 +26,12 @@ import (
|
||||
|
||||
"github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/formatter"
|
||||
"github.com/docker/compose/v2/internal/experimental"
|
||||
xprogress "github.com/moby/buildkit/util/progress/progressui"
|
||||
"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"
|
||||
ui "github.com/docker/compose/v2/pkg/progress"
|
||||
"github.com/docker/compose/v2/pkg/utils"
|
||||
@ -81,13 +82,19 @@ func (opts upOptions) apply(project *types.Project, services []string) (*types.P
|
||||
return project, nil
|
||||
}
|
||||
|
||||
func (opts *upOptions) validateNavigationMenu(dockerCli command.Cli, experimentals *experimental.State) {
|
||||
func (opts *upOptions) validateNavigationMenu(dockerCli command.Cli) {
|
||||
if !dockerCli.Out().IsTerminal() {
|
||||
opts.navigationMenu = false
|
||||
return
|
||||
}
|
||||
// If --menu flag was not set
|
||||
if !opts.navigationMenuChanged {
|
||||
opts.navigationMenu = SetUnchangedOption(ComposeMenu, experimentals.NavBar())
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -102,7 +109,7 @@ func (opts upOptions) OnExit() api.Cascade {
|
||||
}
|
||||
}
|
||||
|
||||
func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, experiments *experimental.State) *cobra.Command {
|
||||
func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
up := upOptions{}
|
||||
create := createOptions{}
|
||||
build := buildOptions{ProjectOptions: p}
|
||||
@ -127,7 +134,7 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
|
||||
return errors.New("cannot combine --attach and --attach-dependencies")
|
||||
}
|
||||
|
||||
up.validateNavigationMenu(dockerCli, experiments)
|
||||
up.validateNavigationMenu(dockerCli)
|
||||
|
||||
if !p.All && len(project.Services) == 0 {
|
||||
return fmt.Errorf("no service selected")
|
||||
@ -158,14 +165,23 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service, ex
|
||||
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.BoolVar(&build.quiet, "quiet-build", false, "Suppress the build output")
|
||||
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.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.")
|
||||
|
||||
flags.BoolVarP(&create.AssumeYes, "yes", "y", false, `Assume "yes" as answer to all prompts and run non-interactively`)
|
||||
flags.SetNormalizeFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
|
||||
// assumeYes was introduced by mistake as `--y`
|
||||
if name == "y" {
|
||||
logrus.Warn("--y is deprecated, please use --yes instead")
|
||||
name = "yes"
|
||||
}
|
||||
return pflag.NormalizedName(name)
|
||||
})
|
||||
return upCmd
|
||||
}
|
||||
|
||||
@ -187,7 +203,14 @@ func validateFlags(up *upOptions, create *createOptions) error {
|
||||
return fmt.Errorf("--build and --no-build are incompatible")
|
||||
}
|
||||
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 up.wait {
|
||||
return fmt.Errorf("--wait cannot be combined with --abort-on-container-exit, --abort-on-container-failure, --attach, --attach-dependencies or --watch")
|
||||
} else {
|
||||
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")
|
||||
@ -201,6 +224,7 @@ func validateFlags(up *upOptions, create *createOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func runUp(
|
||||
ctx context.Context,
|
||||
dockerCli command.Cli,
|
||||
@ -211,6 +235,10 @@ func runUp(
|
||||
project *types.Project,
|
||||
services []string,
|
||||
) error {
|
||||
if err := checksForRemoteStack(ctx, dockerCli, project, buildOptions, createOptions.AssumeYes, []string{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := createOptions.Apply(project)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -234,6 +262,8 @@ func runUp(
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bo.Services = services
|
||||
bo.Deps = !upOptions.noDeps
|
||||
build = &bo
|
||||
}
|
||||
|
||||
@ -247,6 +277,7 @@ func runUp(
|
||||
Inherit: !createOptions.noInherit,
|
||||
Timeout: createOptions.GetTimeout(),
|
||||
QuietPull: createOptions.quietPull,
|
||||
AssumeYes: createOptions.AssumeYes,
|
||||
}
|
||||
|
||||
if upOptions.noStart {
|
||||
@ -301,7 +332,7 @@ func runUp(
|
||||
WaitTimeout: timeout,
|
||||
Watch: upOptions.watch,
|
||||
Services: services,
|
||||
NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain",
|
||||
NavigationMenu: upOptions.navigationMenu && ui.Mode != "plain" && dockerCli.In().IsTerminal(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -47,5 +47,4 @@ func TestApplyScaleOpt(t *testing.T) {
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, *bar.Scale, 3)
|
||||
assert.Equal(t, *bar.Deploy.Replicas, 3)
|
||||
|
||||
}
|
||||
|
@ -59,12 +59,12 @@ func versionCommand(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
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)
|
||||
}
|
||||
|
76
cmd/compose/version_test.go
Normal file
76
cmd/compose/version_test.go
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
Copyright 2025 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 (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
"github.com/docker/compose/v2/internal"
|
||||
"github.com/docker/compose/v2/pkg/mocks"
|
||||
"go.uber.org/mock/gomock"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestVersionCommand(t *testing.T) {
|
||||
originalVersion := internal.Version
|
||||
defer func() {
|
||||
internal.Version = originalVersion
|
||||
}()
|
||||
internal.Version = "v9.9.9-test"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
args: []string{},
|
||||
want: "Docker Compose version v9.9.9-test\n",
|
||||
},
|
||||
{
|
||||
name: "short flag",
|
||||
args: []string{"--short"},
|
||||
want: "9.9.9-test\n",
|
||||
},
|
||||
{
|
||||
name: "json flag",
|
||||
args: []string{"--format", "json"},
|
||||
want: `{"version":"v9.9.9-test"}` + "\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
cli := mocks.NewMockCli(ctrl)
|
||||
cli.EXPECT().Out().Return(streams.NewOut(buf)).AnyTimes()
|
||||
|
||||
cmd := versionCommand(cli)
|
||||
cmd.SetArgs(test.args)
|
||||
err := cmd.Execute()
|
||||
assert.NilError(t, err)
|
||||
|
||||
assert.Equal(t, test.want, buf.String())
|
||||
})
|
||||
}
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
95
cmd/compose/volumes.go
Normal file
95
cmd/compose/volumes.go
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
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"
|
||||
"slices"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type volumesOptions struct {
|
||||
*ProjectOptions
|
||||
Quiet bool
|
||||
Format string
|
||||
}
|
||||
|
||||
func volumesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command {
|
||||
options := volumesOptions{
|
||||
ProjectOptions: p,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "volumes [OPTIONS] [SERVICE...]",
|
||||
Short: "List volumes",
|
||||
RunE: Adapt(func(ctx context.Context, args []string) error {
|
||||
return runVol(ctx, dockerCli, backend, args, options)
|
||||
}),
|
||||
ValidArgsFunction: completeServiceNames(dockerCli, p),
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVarP(&options.Quiet, "quiet", "q", false, "Only display volume names")
|
||||
cmd.Flags().StringVar(&options.Format, "format", "table", flags.FormatHelp)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runVol(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, options volumesOptions) error {
|
||||
project, _, err := options.projectOrName(ctx, dockerCli, services...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
names := project.ServiceNames()
|
||||
|
||||
if len(services) == 0 {
|
||||
services = names
|
||||
}
|
||||
|
||||
for _, service := range services {
|
||||
if !slices.Contains(names, service) {
|
||||
return fmt.Errorf("no such service: %s", service)
|
||||
}
|
||||
}
|
||||
|
||||
volumes, err := backend.Volumes(ctx, project, api.VolumesOptions{
|
||||
Services: services,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if options.Quiet {
|
||||
for _, v := range volumes {
|
||||
_, _ = fmt.Fprintln(dockerCli.Out(), v.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
volumeCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewVolumeFormat(options.Format, options.Quiet),
|
||||
}
|
||||
|
||||
return formatter.VolumeWrite(volumeCtx, volumes)
|
||||
}
|
@ -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
|
||||
|
@ -59,13 +59,13 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
|
||||
}
|
||||
|
||||
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.prune, "prune", true, "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(ctx, dockerCli, nil)
|
||||
project, _, err := watchOpts.ToProject(ctx, dockerCli, services)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -117,9 +117,10 @@ func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, w
|
||||
}
|
||||
|
||||
consumer := formatter.NewLogConsumer(ctx, dockerCli.Out(), dockerCli.Err(), false, false, false)
|
||||
return backend.Watch(ctx, project, services, api.WatchOptions{
|
||||
Build: &build,
|
||||
LogTo: consumer,
|
||||
Prune: watchOpts.prune,
|
||||
return backend.Watch(ctx, project, api.WatchOptions{
|
||||
Build: &build,
|
||||
LogTo: consumer,
|
||||
Prune: watchOpts.prune,
|
||||
Services: services,
|
||||
})
|
||||
}
|
||||
|
@ -27,67 +27,71 @@ var disableAnsi bool
|
||||
func ansi(code string) string {
|
||||
return fmt.Sprintf("\033%s", code)
|
||||
}
|
||||
func SaveCursor() {
|
||||
|
||||
func saveCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("7"))
|
||||
}
|
||||
func RestoreCursor() {
|
||||
|
||||
func restoreCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("8"))
|
||||
}
|
||||
func HideCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("[?25l"))
|
||||
}
|
||||
func ShowCursor() {
|
||||
|
||||
func showCursor() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi("[?25h"))
|
||||
}
|
||||
func MoveCursor(y, x int) {
|
||||
|
||||
func moveCursor(y, x int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi(fmt.Sprintf("[%d;%dH", y, x)))
|
||||
}
|
||||
func MoveCursorX(pos int) {
|
||||
|
||||
func carriageReturn() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dG", pos)))
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dG", 0)))
|
||||
}
|
||||
func ClearLine() {
|
||||
|
||||
func clearLine() {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
// Does not move cursor from its current position
|
||||
fmt.Print(ansi("[2K"))
|
||||
}
|
||||
func MoveCursorUp(lines int) {
|
||||
|
||||
func moveCursorUp(lines int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
// Does not add new lines
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dA", lines)))
|
||||
}
|
||||
func MoveCursorDown(lines int) {
|
||||
|
||||
func moveCursorDown(lines int) {
|
||||
if disableAnsi {
|
||||
return
|
||||
}
|
||||
// Does not add new lines
|
||||
fmt.Print(ansi(fmt.Sprintf("[%dB", lines)))
|
||||
}
|
||||
func NewLine() {
|
||||
|
||||
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
|
||||
|
@ -19,9 +19,10 @@ package formatter
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/cli/cli/command"
|
||||
)
|
||||
|
||||
var names = []string{
|
||||
@ -58,8 +59,11 @@ const (
|
||||
Auto = "auto"
|
||||
)
|
||||
|
||||
// ansiColorOffset is the offset for basic foreground colors in ANSI escape codes.
|
||||
const ansiColorOffset = 30
|
||||
|
||||
// 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
|
||||
@ -68,7 +72,7 @@ func SetANSIMode(streams api.Streams, ansi string) {
|
||||
}
|
||||
}
|
||||
|
||||
func useAnsi(streams api.Streams, ansi string) bool {
|
||||
func useAnsi(streams command.Streams, ansi string) bool {
|
||||
switch ansi {
|
||||
case Always:
|
||||
return true
|
||||
@ -91,11 +95,15 @@ func ansiColor(code, s string, formatOpts ...string) string {
|
||||
|
||||
// Everything about ansiColorCode color https://hyperskill.org/learn/step/18193
|
||||
func ansiColorCode(code string, formatOpts ...string) string {
|
||||
res := "\033["
|
||||
var sb strings.Builder
|
||||
sb.WriteString("\033[")
|
||||
for _, c := range formatOpts {
|
||||
res = fmt.Sprintf("%s%s;", res, c)
|
||||
sb.WriteString(c)
|
||||
sb.WriteString(";")
|
||||
}
|
||||
return fmt.Sprintf("%s%sm", res, code)
|
||||
sb.WriteString(code)
|
||||
sb.WriteString("m")
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func makeColorFunc(code string) colorFunc {
|
||||
@ -104,10 +112,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()
|
||||
@ -120,8 +130,8 @@ func rainbowColor() colorFunc {
|
||||
func init() {
|
||||
colors := map[string]colorFunc{}
|
||||
for i, name := range names {
|
||||
colors[name] = makeColorFunc(strconv.Itoa(30 + i))
|
||||
colors["intense_"+name] = makeColorFunc(strconv.Itoa(30+i) + ";1")
|
||||
colors[name] = makeColorFunc(strconv.Itoa(ansiColorOffset + i))
|
||||
colors["intense_"+name] = makeColorFunc(strconv.Itoa(ansiColorOffset+i) + ";1")
|
||||
}
|
||||
rainbow = []colorFunc{
|
||||
colors["cyan"],
|
||||
|
@ -24,7 +24,7 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/compose/v2/pkg/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
@ -212,9 +212,9 @@ func (c *ContainerContext) Publishers() api.PortPublishers {
|
||||
}
|
||||
|
||||
func (c *ContainerContext) Ports() string {
|
||||
var ports []types.Port
|
||||
var ports []container.Port
|
||||
for _, publisher := range c.c.Publishers {
|
||||
ports = append(ports, types.Port{
|
||||
ports = append(ports, container.Port{
|
||||
IP: publisher.URL,
|
||||
PrivatePort: uint16(publisher.TargetPort),
|
||||
PublicPort: uint16(publisher.PublishedPort),
|
||||
|
@ -56,26 +56,36 @@ func NewLogConsumer(ctx context.Context, stdout, stderr io.Writer, color, prefix
|
||||
}
|
||||
}
|
||||
|
||||
func (l *logConsumer) Register(name string) {
|
||||
l.register(name)
|
||||
}
|
||||
|
||||
func (l *logConsumer) register(name string) *presenter {
|
||||
cf := monochrome
|
||||
if l.color {
|
||||
if name == api.WatchLogger {
|
||||
cf = makeColorFunc("92")
|
||||
} else {
|
||||
cf = nextColor()
|
||||
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 {
|
||||
switch name {
|
||||
case "":
|
||||
cf = monochrome
|
||||
case api.WatchLogger:
|
||||
cf = makeColorFunc("92")
|
||||
default:
|
||||
cf = nextColor()
|
||||
}
|
||||
}
|
||||
p = &presenter{
|
||||
colors: cf,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
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)
|
||||
@ -107,23 +117,15 @@ 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) {
|
||||
@ -157,3 +159,27 @@ func (p *presenter) setPrefix(width int) {
|
||||
}
|
||||
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
|
||||
}
|
||||
|
||||
type logDecorator struct {
|
||||
decorated api.LogConsumer
|
||||
Before func()
|
||||
After func()
|
||||
}
|
||||
|
||||
func (l logDecorator) Log(containerName, message string) {
|
||||
l.Before()
|
||||
l.decorated.Log(containerName, message)
|
||||
l.After()
|
||||
}
|
||||
|
||||
func (l logDecorator) Err(containerName, message string) {
|
||||
l.Before()
|
||||
l.decorated.Err(containerName, message)
|
||||
l.After()
|
||||
}
|
||||
|
||||
func (l logDecorator) Status(container, msg string) {
|
||||
l.Before()
|
||||
l.decorated.Status(container, msg)
|
||||
l.After()
|
||||
}
|
||||
|
@ -29,9 +29,7 @@ import (
|
||||
"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"
|
||||
)
|
||||
|
||||
@ -50,8 +48,8 @@ func (ke *KeyboardError) printError(height int, info string) {
|
||||
if ke.shouldDisplay() {
|
||||
errMessage := ke.err.Error()
|
||||
|
||||
MoveCursor(height-1-extraLines(info)-extraLines(errMessage), 0)
|
||||
ClearLine()
|
||||
moveCursor(height-1-extraLines(info)-extraLines(errMessage), 0)
|
||||
clearLine()
|
||||
|
||||
fmt.Print(errMessage)
|
||||
}
|
||||
@ -71,26 +69,14 @@ func (ke *KeyboardError) error() string {
|
||||
}
|
||||
|
||||
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
|
||||
Watcher Feature
|
||||
}
|
||||
|
||||
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
|
||||
// Feature is an compose feature that can be started/stopped by a menu command
|
||||
type Feature interface {
|
||||
Start(context.Context) error
|
||||
Stop() error
|
||||
}
|
||||
|
||||
type KEYBOARD_LOG_LEVEL int
|
||||
@ -103,42 +89,26 @@ const (
|
||||
|
||||
type LogKeyboard struct {
|
||||
kError KeyboardError
|
||||
Watch KeyboardWatch
|
||||
Watch *KeyboardWatch
|
||||
IsDockerDesktopActive bool
|
||||
IsWatchConfigured bool
|
||||
IsDDComposeUIActive bool
|
||||
logLevel KEYBOARD_LOG_LEVEL
|
||||
signalChannel chan<- os.Signal
|
||||
}
|
||||
|
||||
var KeyboardManager *LogKeyboard
|
||||
var eg multierror.Group
|
||||
|
||||
func NewKeyboardManager(ctx context.Context, isDockerDesktopActive, isWatchConfigured, isDockerDesktopConfigActive 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.IsDDComposeUIActive = isDockerDesktopConfigActive
|
||||
km.logLevel = INFO
|
||||
|
||||
km.Watch.Watching = false
|
||||
km.Watch.WatchFn = watchFn
|
||||
|
||||
km.signalChannel = sc
|
||||
|
||||
KeyboardManager = &km
|
||||
func NewKeyboardManager(isDockerDesktopActive bool, sc chan<- os.Signal) *LogKeyboard {
|
||||
return &LogKeyboard{
|
||||
IsDockerDesktopActive: isDockerDesktopActive,
|
||||
logLevel: INFO,
|
||||
signalChannel: sc,
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) ClearKeyboardInfo() {
|
||||
lk.clearNavigationMenu()
|
||||
func (lk *LogKeyboard) Decorate(l api.LogConsumer) api.LogConsumer {
|
||||
return logDecorator{
|
||||
decorated: l,
|
||||
Before: lk.clearNavigationMenu,
|
||||
After: lk.PrintKeyboardInfo,
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) PrintKeyboardInfo() {
|
||||
@ -163,7 +133,7 @@ func (lk *LogKeyboard) createBuffer(lines int) {
|
||||
|
||||
if lines > 0 {
|
||||
allocateSpace(lines)
|
||||
MoveCursorUp(lines)
|
||||
moveCursorUp(lines)
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,17 +146,17 @@ func (lk *LogKeyboard) printNavigationMenu() {
|
||||
height := goterm.Height()
|
||||
menu := lk.navigationMenu()
|
||||
|
||||
MoveCursorX(0)
|
||||
SaveCursor()
|
||||
carriageReturn()
|
||||
saveCursor()
|
||||
|
||||
lk.kError.printError(height, menu)
|
||||
|
||||
MoveCursor(height-extraLines(menu), 0)
|
||||
ClearLine()
|
||||
moveCursor(height-extraLines(menu), 0)
|
||||
clearLine()
|
||||
fmt.Print(menu)
|
||||
|
||||
MoveCursorX(0)
|
||||
RestoreCursor()
|
||||
carriageReturn()
|
||||
restoreCursor()
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,15 +170,16 @@ func (lk *LogKeyboard) navigationMenu() string {
|
||||
if openDDInfo != "" {
|
||||
openDDUI = navColor(" ")
|
||||
}
|
||||
if lk.IsDDComposeUIActive {
|
||||
if lk.IsDockerDesktopActive {
|
||||
openDDUI = openDDUI + shortcutKeyColor("o") + navColor(" View Config")
|
||||
}
|
||||
|
||||
var watchInfo string
|
||||
if openDDInfo != "" || openDDUI != "" {
|
||||
watchInfo = navColor(" ")
|
||||
}
|
||||
var isEnabled = " Enable"
|
||||
if lk.Watch.Watching {
|
||||
isEnabled := " Enable"
|
||||
if lk.Watch != nil && lk.Watch.Watching {
|
||||
isEnabled = " Disable"
|
||||
}
|
||||
watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch")
|
||||
@ -217,62 +188,66 @@ func (lk *LogKeyboard) navigationMenu() string {
|
||||
|
||||
func (lk *LogKeyboard) clearNavigationMenu() {
|
||||
height := goterm.Height()
|
||||
MoveCursorX(0)
|
||||
SaveCursor()
|
||||
carriageReturn()
|
||||
saveCursor()
|
||||
|
||||
// ClearLine()
|
||||
// clearLine()
|
||||
for i := 0; i < height; i++ {
|
||||
MoveCursorDown(1)
|
||||
ClearLine()
|
||||
moveCursorDown(1)
|
||||
clearLine()
|
||||
}
|
||||
RestoreCursor()
|
||||
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
|
||||
}),
|
||||
)
|
||||
go func() {
|
||||
_ = 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.IsDDComposeUIActive {
|
||||
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
|
||||
}),
|
||||
)
|
||||
go func() {
|
||||
_ = 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
|
||||
}),
|
||||
)
|
||||
go func() {
|
||||
_ = 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) {
|
||||
@ -286,51 +261,54 @@ func (lk *LogKeyboard) keyboardError(prefix string, err error) {
|
||||
}()
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) StartWatch(ctx context.Context, doneCh chan bool, project *types.Project, options api.UpOptions) {
|
||||
if !lk.IsWatchConfigured {
|
||||
if lk.IsDDComposeUIActive {
|
||||
// we try to open watch docs
|
||||
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
|
||||
}))
|
||||
func (lk *LogKeyboard) ToggleWatch(ctx context.Context, options api.UpOptions) {
|
||||
if lk.Watch == nil {
|
||||
return
|
||||
|
||||
}
|
||||
lk.Watch.switchWatching()
|
||||
if !lk.Watch.isWatching() {
|
||||
lk.Watch.Cancel()
|
||||
if lk.Watch.Watching {
|
||||
err := lk.Watch.Watcher.Stop()
|
||||
if err != nil {
|
||||
options.Start.Attach.Err(api.WatchLogger, err.Error())
|
||||
} else {
|
||||
lk.Watch.Watching = false
|
||||
}
|
||||
} 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)
|
||||
go func() {
|
||||
_ = tracing.EventWrapFuncForErrGroup(ctx, "menu/watch", tracing.SpanOptions{},
|
||||
func(ctx context.Context) error {
|
||||
err := lk.Watch.Watcher.Start(ctx)
|
||||
if err != nil {
|
||||
options.Start.Attach.Err(api.WatchLogger, err.Error())
|
||||
} else {
|
||||
lk.Watch.Watching = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
lk.Watch.newContext(ctx)
|
||||
buildOpts := *options.Create.Build
|
||||
buildOpts.Quiet = true
|
||||
return lk.Watch.WatchFn(lk.Watch.Ctx, doneCh, project, options.Start.Services, api.WatchOptions{
|
||||
Build: &buildOpts,
|
||||
LogTo: options.Start.Attach,
|
||||
})
|
||||
}))
|
||||
})()
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (lk *LogKeyboard) HandleKeyEvents(event keyboard.KeyEvent, ctx context.Context, doneCh chan bool, project *types.Project, options api.UpOptions) {
|
||||
func (lk *LogKeyboard) HandleKeyEvents(ctx context.Context, event keyboard.KeyEvent, project *types.Project, options api.UpOptions) {
|
||||
switch kRune := event.Rune; kRune {
|
||||
case 'v':
|
||||
lk.openDockerDesktop(ctx, project)
|
||||
case 'w':
|
||||
lk.StartWatch(ctx, doneCh, project, options)
|
||||
if lk.Watch == nil {
|
||||
// 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
|
||||
go func() {
|
||||
_ = 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
|
||||
})()
|
||||
}()
|
||||
}
|
||||
lk.ToggleWatch(ctx, options)
|
||||
case 'o':
|
||||
lk.openDDComposeUI(ctx, project)
|
||||
}
|
||||
@ -338,25 +316,29 @@ func (lk *LogKeyboard) HandleKeyEvents(event keyboard.KeyEvent, ctx context.Cont
|
||||
case keyboard.KeyCtrlC:
|
||||
_ = keyboard.Close()
|
||||
lk.clearNavigationMenu()
|
||||
ShowCursor()
|
||||
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 (lk *LogKeyboard) EnableWatch(enabled bool, watcher Feature) {
|
||||
lk.Watch = &KeyboardWatch{
|
||||
Watching: enabled,
|
||||
Watcher: watcher,
|
||||
}
|
||||
}
|
||||
|
||||
func allocateSpace(lines int) {
|
||||
for i := 0; i < lines; i++ {
|
||||
ClearLine()
|
||||
NewLine()
|
||||
MoveCursorX(0)
|
||||
clearLine()
|
||||
newLine()
|
||||
carriageReturn()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
"os"
|
||||
|
||||
dockercli "github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/manager"
|
||||
"github.com/docker/cli/cli-plugins/metadata"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/compose/v2/cmd/cmdtrace"
|
||||
@ -62,13 +62,13 @@ func pluginMain() {
|
||||
|
||||
cmd.SetFlagErrorFunc(func(c *cobra.Command, err error) error {
|
||||
return dockercli.StatusError{
|
||||
StatusCode: compose.CommandSyntaxFailure.ExitCode,
|
||||
StatusCode: 1,
|
||||
Status: err.Error(),
|
||||
}
|
||||
})
|
||||
return cmd
|
||||
},
|
||||
manager.Metadata{
|
||||
metadata.Metadata{
|
||||
SchemaVersion: "0.1.0",
|
||||
Vendor: "Docker Inc.",
|
||||
Version: internal.Version,
|
||||
|
152
docs/examples/provider.go
Normal file
152
docs/examples/provider.go
Normal file
@ -0,0 +1,152 @@
|
||||
/*
|
||||
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 main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd := &cobra.Command{
|
||||
Short: "Compose Provider Example",
|
||||
Use: "demo",
|
||||
}
|
||||
cmd.AddCommand(composeCommand())
|
||||
err := cmd.Execute()
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
type options struct {
|
||||
db string
|
||||
size int
|
||||
}
|
||||
|
||||
func composeCommand() *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "compose EVENT",
|
||||
TraverseChildren: true,
|
||||
}
|
||||
c.PersistentFlags().String("project-name", "", "compose project name") // unused
|
||||
|
||||
var options options
|
||||
upCmd := &cobra.Command{
|
||||
Use: "up",
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
up(options, args)
|
||||
},
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
|
||||
upCmd.Flags().StringVar(&options.db, "type", "", "Database type (mysql, postgres, etc.)")
|
||||
_ = upCmd.MarkFlagRequired("type")
|
||||
upCmd.Flags().IntVar(&options.size, "size", 10, "Database size in GB")
|
||||
upCmd.Flags().String("name", "", "Name of the database to be created")
|
||||
_ = upCmd.MarkFlagRequired("name")
|
||||
|
||||
downCmd := &cobra.Command{
|
||||
Use: "down",
|
||||
Run: down,
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
downCmd.Flags().String("name", "", "Name of the database to be deleted")
|
||||
_ = downCmd.MarkFlagRequired("name")
|
||||
|
||||
c.AddCommand(upCmd, downCmd)
|
||||
c.AddCommand(metadataCommand(upCmd, downCmd))
|
||||
return c
|
||||
}
|
||||
|
||||
const lineSeparator = "\n"
|
||||
|
||||
func up(options options, args []string) {
|
||||
servicename := args[0]
|
||||
fmt.Printf(`{ "type": "debug", "message": "Starting %s" }%s`, servicename, lineSeparator)
|
||||
|
||||
for i := 0; i < options.size; i += 10 {
|
||||
time.Sleep(1 * time.Second)
|
||||
fmt.Printf(`{ "type": "info", "message": "Processing ... %d%%" }%s`, i*100/options.size, lineSeparator)
|
||||
}
|
||||
fmt.Printf(`{ "type": "setenv", "message": "URL=https://magic.cloud/%s" }%s`, servicename, lineSeparator)
|
||||
}
|
||||
|
||||
func down(_ *cobra.Command, _ []string) {
|
||||
fmt.Printf(`{ "type": "error", "message": "Permission error" }%s`, lineSeparator)
|
||||
}
|
||||
|
||||
func metadataCommand(upCmd, downCmd *cobra.Command) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "metadata",
|
||||
Run: func(cmd *cobra.Command, _ []string) {
|
||||
metadata(upCmd, downCmd)
|
||||
},
|
||||
Args: cobra.NoArgs,
|
||||
}
|
||||
}
|
||||
|
||||
func metadata(upCmd, downCmd *cobra.Command) {
|
||||
metadata := ProviderMetadata{}
|
||||
metadata.Description = "Manage services on AwesomeCloud"
|
||||
metadata.Up = commandParameters(upCmd)
|
||||
metadata.Down = commandParameters(downCmd)
|
||||
jsonMetadata, err := json.Marshal(metadata)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(string(jsonMetadata))
|
||||
}
|
||||
|
||||
func commandParameters(cmd *cobra.Command) CommandMetadata {
|
||||
cmdMetadata := CommandMetadata{}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
_, isRequired := f.Annotations[cobra.BashCompOneRequiredFlag]
|
||||
cmdMetadata.Parameters = append(cmdMetadata.Parameters, Metadata{
|
||||
Name: f.Name,
|
||||
Description: f.Usage,
|
||||
Required: isRequired,
|
||||
Type: f.Value.Type(),
|
||||
Default: f.DefValue,
|
||||
})
|
||||
})
|
||||
return cmdMetadata
|
||||
}
|
||||
|
||||
type ProviderMetadata struct {
|
||||
Description string `json:"description"`
|
||||
Up CommandMetadata `json:"up"`
|
||||
Down CommandMetadata `json:"down"`
|
||||
}
|
||||
|
||||
type CommandMetadata struct {
|
||||
Parameters []Metadata `json:"parameters"`
|
||||
}
|
||||
|
||||
type Metadata struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Required bool `json:"required"`
|
||||
Type string `json:"type"`
|
||||
Default string `json:"default,omitempty"`
|
||||
}
|
176
docs/extension.md
Normal file
176
docs/extension.md
Normal file
@ -0,0 +1,176 @@
|
||||
# About
|
||||
|
||||
The Compose application model defines `service` as an abstraction for a computing unit managing (a subset of)
|
||||
application needs, which can interact with other service by relying on network(s). Docker Compose is designed
|
||||
to use the Docker Engine ("Moby") API to manage services as containers, but the abstraction _could_ also cover
|
||||
many other runtimes, typically cloud services or services natively provided by host.
|
||||
|
||||
The Compose extensibility model has been designed to extend the `service` support to runtimes accessible through
|
||||
third-party tooling.
|
||||
|
||||
# Architecture
|
||||
|
||||
Compose extensibility relies on the `provider` attribute to select the actual binary responsible for managing
|
||||
the resource(s) needed to run a service.
|
||||
|
||||
```yaml
|
||||
database:
|
||||
provider:
|
||||
type: awesomecloud
|
||||
options:
|
||||
type: mysql
|
||||
size: 256
|
||||
name: myAwesomeCloudDB
|
||||
```
|
||||
|
||||
`provider.type` tells Compose the binary to run, which can be either:
|
||||
- Another Docker CLI plugin (typically, `model` to run `docker-model`)
|
||||
- An executable in user's `PATH`
|
||||
|
||||
If `provider.type` doesn't resolve into any of those, Compose will report an error and interrupt the `up` command.
|
||||
|
||||
To be a valid Compose extension, provider command *MUST* accept a `compose` command (which can be hidden)
|
||||
with subcommands `up` and `down`.
|
||||
|
||||
## Up lifecycle
|
||||
|
||||
To execute an application's `up` lifecycle, Compose executes the provider's `compose up` command, passing
|
||||
the project name, service name, and additional options. The `provider.options` are translated
|
||||
into command line flags. For example:
|
||||
```console
|
||||
awesomecloud compose --project-name <NAME> up --type=mysql --size=256 "database"
|
||||
```
|
||||
|
||||
> __Note:__ `project-name` _should_ be used by the provider to tag resources
|
||||
> set for project, so that later execution with `down` subcommand releases
|
||||
> all allocated resources set for the project.
|
||||
|
||||
## Communication with Compose
|
||||
|
||||
Providers can interact with Compose using `stdout` as a channel, sending JSON line delimited messages.
|
||||
JSON messages MUST include a `type` and a `message` attribute.
|
||||
```json
|
||||
{ "type": "info", "message": "preparing mysql ..." }
|
||||
```
|
||||
|
||||
`type` can be either:
|
||||
- `info`: Reports status updates to the user. Compose will render message as the service state in the progress UI
|
||||
- `error`: Let's the user know something went wrong with details about the error. Compose will render the message as the reason for the service failure.
|
||||
- `setenv`: Let's the plugin tell Compose how dependent services can access the created resource. See next section for further details.
|
||||
- `debug`: Those messages could help debugging the provider, but are not rendered to the user by default. They are rendered when Compose is started with `--verbose` flag.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Shell->>Compose: docker compose up
|
||||
Compose->>Provider: compose up --project-name=xx --foo=bar "database"
|
||||
Provider--)Compose: json { "info": "pulling 25%" }
|
||||
Compose-)Shell: pulling 25%
|
||||
Provider--)Compose: json { "info": "pulling 50%" }
|
||||
Compose-)Shell: pulling 50%
|
||||
Provider--)Compose: json { "info": "pulling 75%" }
|
||||
Compose-)Shell: pulling 75%
|
||||
Provider--)Compose: json { "setenv": "URL=http://cloud.com/abcd:1234" }
|
||||
Compose-)Compose: set DATABASE_URL
|
||||
Provider-)Compose: EOF (command complete) exit 0
|
||||
Compose-)Shell: service started
|
||||
```
|
||||
|
||||
## Connection to a service managed by a provider
|
||||
|
||||
A service in the Compose application can declare dependency on a service managed by an external provider:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
image: myapp
|
||||
depends_on:
|
||||
- database
|
||||
|
||||
database:
|
||||
provider:
|
||||
type: awesomecloud
|
||||
```
|
||||
|
||||
When the provider command sends a `setenv` JSON message, Compose injects the specified variable into any dependent service,
|
||||
automatically prefixing it with the service name. For example, if `awesomecloud compose up` returns:
|
||||
```json
|
||||
{"type": "setenv", "message": "URL=https://awesomecloud.com/db:1234"}
|
||||
```
|
||||
Then the `app` service, which depends on the service managed by the provider, will receive a `DATABASE_URL` environment variable injected
|
||||
into its runtime environment.
|
||||
|
||||
> __Note:__ The `compose up` provider command _MUST_ be idempotent. If resource is already running, the command _MUST_ set
|
||||
> the same environment variables to ensure consistent configuration of dependent services.
|
||||
|
||||
## Down lifecycle
|
||||
|
||||
`down` lifecycle is equivalent to `up` with the `<provider> compose --project-name <NAME> down <SERVICE>` command.
|
||||
The provider is responsible for releasing all resources associated with the service.
|
||||
|
||||
## Provide metadata about options
|
||||
|
||||
Compose extensions *MAY* optionally implement a `metadata` subcommand to provide information about the parameters accepted by the `up` and `down` commands.
|
||||
|
||||
The `metadata` subcommand takes no parameters and returns a JSON structure on the `stdout` channel that describes the parameters accepted by both the `up` and `down` commands, including whether each parameter is mandatory or optional.
|
||||
|
||||
```console
|
||||
awesomecloud compose metadata
|
||||
```
|
||||
|
||||
The expected JSON output format is:
|
||||
```json
|
||||
{
|
||||
"description": "Manage services on AwesomeCloud",
|
||||
"up": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "type",
|
||||
"description": "Database type (mysql, postgres, etc.)",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "size",
|
||||
"description": "Database size in GB",
|
||||
"required": false,
|
||||
"type": "integer",
|
||||
"default": "10"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the database to be created",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
},
|
||||
"down": {
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"description": "Name of the database to be removed",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
The top elements are:
|
||||
- `description`: Human-readable description of the provider
|
||||
- `up`: Object describing the parameters accepted by the `up` command
|
||||
- `down`: Object describing the parameters accepted by the `down` command
|
||||
|
||||
And for each command parameter, you should include the following properties:
|
||||
- `name`: The parameter name (without `--` prefix)
|
||||
- `description`: Human-readable description of the parameter
|
||||
- `required`: Boolean indicating if the parameter is mandatory
|
||||
- `type`: Parameter type (`string`, `integer`, `boolean`, etc.)
|
||||
- `default`: Default value (optional, only for non-required parameters)
|
||||
- `enum`: List of possible values supported by the parameter separated by `,` (optional, only for parameters with a limited set of values)
|
||||
|
||||
This metadata allows Compose and other tools to understand the provider's interface and provide better user experience, such as validation, auto-completion, and documentation generation.
|
||||
|
||||
## Examples
|
||||
|
||||
See [example](examples/provider.go) for illustration on implementing this API in a command line
|
@ -1,163 +1,27 @@
|
||||
# docker compose
|
||||
|
||||
```text
|
||||
docker compose [-f <arg>...] [options] [COMMAND] [ARGS...]
|
||||
```
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
You can use the compose subcommand, `docker compose [-f <arg>...] [options] [COMMAND] [ARGS...]`, to build and manage
|
||||
multiple services in Docker containers.
|
||||
|
||||
### 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.
|
||||
|
||||
#### Specifying multiple Compose files
|
||||
You can supply multiple `-f` configuration files. When you supply multiple files, Compose combines them into a single
|
||||
configuration. Compose builds the configuration in the order you supply the files. Subsequent files override and add
|
||||
to their predecessors.
|
||||
|
||||
For example, consider this command line:
|
||||
|
||||
```console
|
||||
$ docker compose -f docker-compose.yml -f docker-compose.admin.yml run backup_db
|
||||
```
|
||||
|
||||
The `docker-compose.yml` file might specify a `webapp` service.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
webapp:
|
||||
image: examples/web
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
- "/data"
|
||||
```
|
||||
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
|
||||
New values, add to the `webapp` service configuration.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
webapp:
|
||||
build: .
|
||||
environment:
|
||||
- DEBUG=1
|
||||
```
|
||||
|
||||
When you use multiple Compose files, all paths in the files are relative to the first configuration file specified
|
||||
with `-f`. You can use the `--project-directory` option to override this base path.
|
||||
|
||||
Use a `-f` with `-` (dash) as the filename to read the configuration from stdin. When stdin is used all paths in the
|
||||
configuration are relative to the current working directory.
|
||||
|
||||
The `-f` flag is optional. If you don’t provide this flag on the command line, Compose traverses the working directory
|
||||
and its parent directories looking for a `compose.yaml` or `docker-compose.yaml` file.
|
||||
|
||||
#### Specifying a path to a single Compose file
|
||||
You can use the `-f` flag to specify a path to a Compose file that is not located in the current directory, either
|
||||
from the command line or by setting up a `COMPOSE_FILE` environment variable in your shell or in an environment file.
|
||||
|
||||
For an example of using the `-f` option at the command line, suppose you are running the Compose Rails sample, and
|
||||
have a `compose.yaml` file in a directory called `sandbox/rails`. You can use a command like `docker compose pull` to
|
||||
get the postgres image for the db service from anywhere by using the `-f` flag as follows:
|
||||
|
||||
```console
|
||||
$ docker compose -f ~/sandbox/rails/compose.yaml pull db
|
||||
```
|
||||
|
||||
### Use `-p` to specify a project name
|
||||
|
||||
Each configuration has a project name. Compose sets the project name using
|
||||
the following mechanisms, in order of precedence:
|
||||
- The `-p` command line flag
|
||||
- The `COMPOSE_PROJECT_NAME` environment variable
|
||||
- The top level `name:` variable from the config file (or the last `name:`
|
||||
from a series of config files specified using `-f`)
|
||||
- The `basename` of the project directory containing the config file (or
|
||||
containing the first config file specified using `-f`)
|
||||
- The `basename` of the current directory if no config file is specified
|
||||
Project names must contain only lowercase letters, decimal digits, dashes,
|
||||
and underscores, and must begin with a lowercase letter or decimal digit. If
|
||||
the `basename` of the project directory or current directory violates this
|
||||
constraint, you must use one of the other mechanisms.
|
||||
|
||||
```console
|
||||
$ docker compose -p my_project ps -a
|
||||
NAME SERVICE STATUS PORTS
|
||||
my_project_demo_1 demo running
|
||||
|
||||
$ docker compose -p my_project logs
|
||||
demo_1 | PING localhost (127.0.0.1): 56 data bytes
|
||||
demo_1 | 64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.095 ms
|
||||
```
|
||||
|
||||
### Use profiles to enable optional services
|
||||
|
||||
Use `--profile` to specify one or more active profiles
|
||||
Calling `docker compose --profile frontend up` starts the services with the profile `frontend` and services
|
||||
without any specified profiles.
|
||||
You can also enable multiple profiles, e.g. with `docker compose --profile frontend --profile debug up` the profiles `frontend` and `debug` is enabled.
|
||||
|
||||
Profiles can also be set by `COMPOSE_PROFILES` environment variable.
|
||||
|
||||
### Configuring parallelism
|
||||
|
||||
Use `--parallel` to specify the maximum level of parallelism for concurrent engine calls.
|
||||
Calling `docker compose --parallel 1 pull` pulls the pullable images defined in the Compose file
|
||||
one at a time. This can also be used to control build concurrency.
|
||||
|
||||
Parallelism can also be set by the `COMPOSE_PARALLEL_LIMIT` environment variable.
|
||||
|
||||
### Set up environment variables
|
||||
|
||||
You can set environment variables for various docker compose options, including the `-f`, `-p` and `--profiles` flags.
|
||||
|
||||
Setting the `COMPOSE_FILE` environment variable is equivalent to passing the `-f` flag,
|
||||
`COMPOSE_PROJECT_NAME` environment variable does the same as the `-p` flag,
|
||||
`COMPOSE_PROFILES` environment variable is equivalent to the `--profiles` flag
|
||||
and `COMPOSE_PARALLEL_LIMIT` does the same as the `--parallel` flag.
|
||||
|
||||
If flags are explicitly set on the command line, the associated environment variable is ignored.
|
||||
|
||||
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.
|
||||
Dry Run mode shows you all the steps Compose applies when executing a command, for example:
|
||||
```console
|
||||
$ docker compose --dry-run up --build -d
|
||||
[+] Pulling 1/1
|
||||
✔ DRY-RUN MODE - db Pulled 0.9s
|
||||
[+] Running 10/8
|
||||
✔ DRY-RUN MODE - build service backend 0.0s
|
||||
✔ DRY-RUN MODE - ==> ==> writing image dryRun-754a08ddf8bcb1cf22f310f09206dd783d42f7dd 0.0s
|
||||
✔ DRY-RUN MODE - ==> ==> naming to nginx-golang-mysql-backend 0.0s
|
||||
✔ DRY-RUN MODE - Network nginx-golang-mysql_default Created 0.0s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-db-1 Created 0.0s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-backend-1 Created 0.0s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-proxy-1 Created 0.0s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-db-1 Healthy 0.5s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-backend-1 Started 0.0s
|
||||
✔ DRY-RUN MODE - Container nginx-golang-mysql-proxy-1 Started Started
|
||||
```
|
||||
From the example above, you can see that the first step is to pull the image defined by `db` service, then build the `backend` service.
|
||||
Next, the containers are created. The `db` service is started, and the `backend` and `proxy` wait until the `db` service is healthy before starting.
|
||||
|
||||
Dry Run mode works with almost all commands. You cannot use Dry Run mode with a command that doesn't change the state of a Compose stack such as `ps`, `ls`, `logs` for example.
|
||||
Define and run multi-container applications with Docker
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:--------------------------------|:----------------------------------------------------------------------------------------|
|
||||
| [`attach`](compose_attach.md) | Attach local standard input, output, and error streams to a service's running container |
|
||||
| [`bridge`](compose_bridge.md) | Convert compose files into another model |
|
||||
| [`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 |
|
||||
@ -165,6 +29,7 @@ Dry Run mode works with almost all commands. You cannot use Dry Run mode with a
|
||||
| [`pause`](compose_pause.md) | Pause services |
|
||||
| [`port`](compose_port.md) | Print the public port for a port binding |
|
||||
| [`ps`](compose_ps.md) | List containers |
|
||||
| [`publish`](compose_publish.md) | Publish compose application |
|
||||
| [`pull`](compose_pull.md) | Pull service images |
|
||||
| [`push`](compose_push.md) | Push service images |
|
||||
| [`restart`](compose_restart.md) | Restart service containers |
|
||||
@ -178,7 +43,8 @@ Dry Run mode works with almost all commands. You cannot use Dry Run mode with a
|
||||
| [`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 |
|
||||
| [`volumes`](compose_volumes.md) | List volumes |
|
||||
| [`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 |
|
||||
|
||||
|
||||
@ -194,20 +60,17 @@ Dry Run mode works with almost all commands. You cannot use Dry Run mode with a
|
||||
| `-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, json, quiet) |
|
||||
| `--progress` | `string` | | 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.
|
||||
Use the `-f` flag to specify the location of a Compose [configuration file](/reference/compose-file/).
|
||||
|
||||
#### Specifying multiple Compose files
|
||||
You can supply multiple `-f` configuration files. When you supply multiple files, Compose combines them into a single
|
||||
@ -217,10 +80,10 @@ to their predecessors.
|
||||
For example, consider this command line:
|
||||
|
||||
```console
|
||||
$ docker compose -f docker-compose.yml -f docker-compose.admin.yml run backup_db
|
||||
$ docker compose -f compose.yaml -f compose.admin.yaml run backup_db
|
||||
```
|
||||
|
||||
The `docker-compose.yml` file might specify a `webapp` service.
|
||||
The `compose.yaml` file might specify a `webapp` service.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
@ -231,7 +94,7 @@ services:
|
||||
volumes:
|
||||
- "/data"
|
||||
```
|
||||
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
|
||||
If the `compose.admin.yaml` also specifies this same service, any matching fields override the previous file.
|
||||
New values, add to the `webapp` service configuration.
|
||||
|
||||
```yaml
|
||||
@ -346,4 +209,4 @@ $ docker compose --dry-run up --build -d
|
||||
From the example above, you can see that the first step is to pull the image defined by `db` service, then build the `backend` service.
|
||||
Next, the containers are created. The `db` service is started, and the `backend` and `proxy` wait until the `db` service is healthy before starting.
|
||||
|
||||
Dry Run mode works with almost all commands. You cannot use Dry Run mode with a command that doesn't change the state of a Compose stack such as `ps`, `ls`, `logs` for example.
|
||||
Dry Run mode works with almost all commands. You cannot use Dry Run mode with a command that doesn't change the state of a Compose stack such as `ps`, `ls`, `logs` for example.
|
||||
|
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-->
|
||||
|
@ -8,8 +8,10 @@ Publish compose application
|
||||
| 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) |
|
||||
| `--oci-version` | `string` | | OCI image/artifact specification version (automatically determined by default) |
|
||||
| `--resolve-image-digests` | `bool` | | Pin image tags to digests |
|
||||
| `--with-env` | `bool` | | Include environment variables in the published OCI artifact |
|
||||
| `-y`, `--yes` | `bool` | | Assume "yes" as answer to all prompts |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
22
docs/reference/compose_bridge.md
Normal file
22
docs/reference/compose_bridge.md
Normal file
@ -0,0 +1,22 @@
|
||||
# docker compose bridge
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Convert compose files into another model
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:-------------------------------------------------------|:-----------------------------------------------------------------------------|
|
||||
| [`convert`](compose_bridge_convert.md) | Convert compose files to Kubernetes manifests, Helm charts, or another model |
|
||||
| [`transformations`](compose_bridge_transformations.md) | Manage transformation images |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-------|:--------|:--------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
17
docs/reference/compose_bridge_convert.md
Normal file
17
docs/reference/compose_bridge_convert.md
Normal file
@ -0,0 +1,17 @@
|
||||
# docker compose bridge convert
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Convert compose files to Kubernetes manifests, Helm charts, or another model
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------------|:--------------|:--------|:-------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-o`, `--output` | `string` | `out` | The output directory for the Kubernetes resources |
|
||||
| `--templates` | `string` | | Directory containing transformation templates |
|
||||
| `-t`, `--transformation` | `stringArray` | | Transformation to apply to compose model (default: docker/compose-bridge-kubernetes) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
22
docs/reference/compose_bridge_transformations.md
Normal file
22
docs/reference/compose_bridge_transformations.md
Normal file
@ -0,0 +1,22 @@
|
||||
# docker compose bridge transformations
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Manage transformation images
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:-----------------------------------------------------|:-------------------------------|
|
||||
| [`create`](compose_bridge_transformations_create.md) | Create a new transformation |
|
||||
| [`list`](compose_bridge_transformations_list.md) | List available transformations |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-------|:--------|:--------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
15
docs/reference/compose_bridge_transformations_create.md
Normal file
15
docs/reference/compose_bridge_transformations_create.md
Normal file
@ -0,0 +1,15 @@
|
||||
# docker compose bridge transformations create
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Create a new transformation
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------|:---------|:--------|:----------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `-f`, `--from` | `string` | | Existing transformation to copy (default: docker/compose-bridge-kubernetes) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
20
docs/reference/compose_bridge_transformations_list.md
Normal file
20
docs/reference/compose_bridge_transformations_list.md
Normal file
@ -0,0 +1,20 @@
|
||||
# docker compose bridge transformations list
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
List available transformations
|
||||
|
||||
### Aliases
|
||||
|
||||
`docker compose bridge transformations list`, `docker compose bridge transformations ls`
|
||||
|
||||
### Options
|
||||
|
||||
| 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 transformer names |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
@ -4,9 +4,9 @@
|
||||
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,
|
||||
[image](https://github.com/compose-spec/compose-spec/blob/main/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).
|
||||
[variable interpolation](https://github.com/compose-spec/compose-spec/blob/main/spec.md#interpolation).
|
||||
|
||||
If you change a service's `Dockerfile` or the contents of its build directory,
|
||||
run `docker compose build` to rebuild it.
|
||||
@ -17,12 +17,16 @@ run `docker compose build` to rebuild it.
|
||||
|:----------------------|:--------------|:--------|:------------------------------------------------------------------------------------------------------------|
|
||||
| `--build-arg` | `stringArray` | | Set build-time variables for services |
|
||||
| `--builder` | `string` | | Set builder to use |
|
||||
| `--check` | `bool` | | Check build configuration |
|
||||
| `--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 |
|
||||
| `--print` | `bool` | | Print equivalent bake file |
|
||||
| `--provenance` | `string` | | Add a provenance attestation |
|
||||
| `--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 |
|
||||
| `-q`, `--quiet` | `bool` | | Suppress the build output |
|
||||
| `--sbom` | `string` | | Add a SBOM attestation |
|
||||
| `--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) |
|
||||
|
||||
@ -34,9 +38,9 @@ run `docker compose build` to rebuild it.
|
||||
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,
|
||||
[image](https://github.com/compose-spec/compose-spec/blob/main/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).
|
||||
[variable interpolation](https://github.com/compose-spec/compose-spec/blob/main/spec.md#interpolation).
|
||||
|
||||
If you change a service's `Dockerfile` or the contents of its build directory,
|
||||
run `docker compose build` to rebuild it.
|
||||
|
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-->
|
||||
|
@ -5,20 +5,20 @@
|
||||
It merges the Compose files set by `-f` flags, resolves variables in the Compose file, and expands short-notation into
|
||||
the canonical format.
|
||||
|
||||
### Aliases
|
||||
|
||||
`docker compose config`, `docker compose convert`
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------------------|:---------|:--------|:----------------------------------------------------------------------------|
|
||||
| `--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] |
|
||||
| `--format` | `string` | | Format the output. Values: [yaml \| json] |
|
||||
| `--hash` | `string` | | Print the service config hash, one per line. |
|
||||
| `--images` | `bool` | | Print the image names, one per line. |
|
||||
| `--lock-image-digests` | `bool` | | Produces an override file with image digests |
|
||||
| `--models` | `bool` | | Print the model names, one per line. |
|
||||
| `--networks` | `bool` | | Print the network names, one per line. |
|
||||
| `--no-consistency` | `bool` | | Don't check model consistency - warning: may produce invalid Compose output |
|
||||
| `--no-env-resolution` | `bool` | | Don't resolve service env files |
|
||||
| `--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 |
|
||||
|
@ -7,6 +7,7 @@ Copy files/folders between a service container and the local filesystem
|
||||
|
||||
| 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 |
|
||||
|
@ -16,6 +16,7 @@ Creates containers for a service
|
||||
| `--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`, `--yes` | `bool` | | Assume "yes" as answer to all prompts and run non-interactively |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
@ -23,10 +23,12 @@ The events that can be received using this can be seen [here](/reference/cli/doc
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------|:-------|:--------|:------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--json` | `bool` | | 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 |
|
||||
| `--since` | `string` | | Show all events created since timestamp |
|
||||
| `--until` | `string` | | Stream events until this timestamp |
|
||||
|
||||
|
||||
<!---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-->
|
||||
|
@ -4,7 +4,7 @@
|
||||
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
|
||||
```
|
||||
|
||||
### Options
|
||||
@ -23,5 +23,5 @@ $ docker-compose kill -s SIGINT
|
||||
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
|
||||
```
|
||||
|
@ -11,7 +11,7 @@ Lists running 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 |
|
||||
| `-q`, `--quiet` | `bool` | | Only display project names |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
18
docs/reference/compose_publish.md
Normal file
18
docs/reference/compose_publish.md
Normal file
@ -0,0 +1,18 @@
|
||||
# docker compose publish
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Publish compose application
|
||||
|
||||
### Options
|
||||
|
||||
| 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 |
|
||||
| `--with-env` | `bool` | | Include environment variables in the published OCI artifact |
|
||||
| `-y`, `--yes` | `bool` | | Assume "yes" as answer to all prompts |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
@ -9,8 +9,8 @@ after a container is built, but before the container's command is executed) are
|
||||
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).
|
||||
[restart](https://github.com/compose-spec/compose-spec/blob/main/spec.md#restart)
|
||||
or [restart_policy](https://github.com/compose-spec/compose-spec/blob/main/deploy.md#restart_policy).
|
||||
|
||||
### Options
|
||||
|
||||
@ -33,5 +33,5 @@ after a container is built, but before the container's command is executed) are
|
||||
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).
|
||||
[restart](https://github.com/compose-spec/compose-spec/blob/main/spec.md#restart)
|
||||
or [restart_policy](https://github.com/compose-spec/compose-spec/blob/main/deploy.md#restart_policy).
|
||||
|
@ -57,29 +57,33 @@ specified in the service configuration.
|
||||
|
||||
### Options
|
||||
|
||||
| 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 |
|
||||
| `--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 |
|
||||
| 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 |
|
||||
| `--env-from-file` | `stringArray` | | Set environment variables from file |
|
||||
| `-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") |
|
||||
| `-q`, `--quiet` | `bool` | | Don't print anything to STDOUT |
|
||||
| `--quiet-build` | `bool` | | Suppress progress output from the build process |
|
||||
| `--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,13 +5,13 @@ Display a live stream of container(s) resource usage statistics
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `-a`, `--all` | `bool` | | Show all containers (default shows just running) |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--format` | `string` | | 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-stream` | `bool` | | Disable streaming stats and only pull the first result |
|
||||
| `--no-trunc` | `bool` | | Do not truncate output |
|
||||
| Name | Type | Default | Description |
|
||||
|:--------------|:---------|:--------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `-a`, `--all` | `bool` | | Show all containers (default shows just running) |
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--format` | `string` | | 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/engine/cli/formatting/ for more information about formatting output with templates |
|
||||
| `--no-stream` | `bool` | | Disable streaming stats and only pull the first result |
|
||||
| `--no-trunc` | `bool` | | Do not truncate output |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
@ -44,6 +44,7 @@ If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the contai
|
||||
| `--no-recreate` | `bool` | | If containers already exist, don't recreate them. Incompatible with --force-recreate. |
|
||||
| `--no-start` | `bool` | | Don't start the services after creating them |
|
||||
| `--pull` | `string` | `policy` | Pull image before running ("always"\|"missing"\|"never") |
|
||||
| `--quiet-build` | `bool` | | Suppress the build output |
|
||||
| `--quiet-pull` | `bool` | | Pull without printing progress information |
|
||||
| `--remove-orphans` | `bool` | | Remove containers for services not defined in the Compose file |
|
||||
| `-V`, `--renew-anon-volumes` | `bool` | | Recreate anonymous volumes instead of retrieving data from the previous containers |
|
||||
@ -51,8 +52,9 @@ If the process is interrupted using `SIGINT` (ctrl + C) or `SIGTERM`, the contai
|
||||
| `-t`, `--timeout` | `int` | `0` | Use this timeout in seconds for container shutdown when attached or when containers are already running |
|
||||
| `--timestamps` | `bool` | | Show timestamps |
|
||||
| `--wait` | `bool` | | Wait for services to be running\|healthy. Implies detached mode. |
|
||||
| `--wait-timeout` | `int` | `0` | Maximum duration to wait for the project to be running\|healthy |
|
||||
| `--wait-timeout` | `int` | `0` | Maximum duration in seconds to wait for the project to be running\|healthy |
|
||||
| `-w`, `--watch` | `bool` | | Watch source code and rebuild/refresh containers when files are updated. |
|
||||
| `-y`, `--yes` | `bool` | | Assume "yes" as answer to all prompts and run non-interactively |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
16
docs/reference/compose_volumes.md
Normal file
16
docs/reference/compose_volumes.md
Normal file
@ -0,0 +1,16 @@
|
||||
# docker compose volumes
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
List volumes
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------|:---------|:--------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--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 |
|
||||
| `-q`, `--quiet` | `bool` | | Only display volume names |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
@ -1,7 +1,7 @@
|
||||
# docker compose wait
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Block until the first service container stops
|
||||
Block until containers of all (or specified) services stop.
|
||||
|
||||
### Options
|
||||
|
||||
|
@ -9,7 +9,7 @@ Watch build context for service and rebuild/refresh containers when files are up
|
||||
|:------------|:-------|:--------|:----------------------------------------------|
|
||||
| `--dry-run` | `bool` | | Execute command in dry run mode |
|
||||
| `--no-up` | `bool` | | Do not build & start services before watching |
|
||||
| `--prune` | `bool` | | Prune dangling images on rebuild |
|
||||
| `--prune` | `bool` | `true` | Prune dangling images on rebuild |
|
||||
| `--quiet` | `bool` | | hide build output |
|
||||
|
||||
|
||||
|
@ -1,11 +1,240 @@
|
||||
command: docker compose
|
||||
short: Docker Compose
|
||||
long: |-
|
||||
You can use the compose subcommand, `docker compose [-f <arg>...] [options] [COMMAND] [ARGS...]`, to build and manage
|
||||
multiple services in Docker containers.
|
||||
|
||||
long: Define and run multi-container applications with Docker
|
||||
usage: docker compose
|
||||
pname: docker
|
||||
plink: docker.yaml
|
||||
cname:
|
||||
- docker compose attach
|
||||
- docker compose bridge
|
||||
- docker compose build
|
||||
- docker compose commit
|
||||
- docker compose config
|
||||
- docker compose cp
|
||||
- docker compose create
|
||||
- docker compose down
|
||||
- docker compose events
|
||||
- docker compose exec
|
||||
- docker compose export
|
||||
- docker compose images
|
||||
- docker compose kill
|
||||
- docker compose logs
|
||||
- docker compose ls
|
||||
- docker compose pause
|
||||
- docker compose port
|
||||
- docker compose ps
|
||||
- docker compose publish
|
||||
- docker compose pull
|
||||
- docker compose push
|
||||
- docker compose restart
|
||||
- docker compose rm
|
||||
- docker compose run
|
||||
- docker compose scale
|
||||
- docker compose start
|
||||
- docker compose stats
|
||||
- docker compose stop
|
||||
- docker compose top
|
||||
- docker compose unpause
|
||||
- docker compose up
|
||||
- docker compose version
|
||||
- docker compose volumes
|
||||
- docker compose wait
|
||||
- docker compose watch
|
||||
clink:
|
||||
- docker_compose_attach.yaml
|
||||
- docker_compose_bridge.yaml
|
||||
- docker_compose_build.yaml
|
||||
- docker_compose_commit.yaml
|
||||
- docker_compose_config.yaml
|
||||
- docker_compose_cp.yaml
|
||||
- docker_compose_create.yaml
|
||||
- docker_compose_down.yaml
|
||||
- docker_compose_events.yaml
|
||||
- docker_compose_exec.yaml
|
||||
- docker_compose_export.yaml
|
||||
- docker_compose_images.yaml
|
||||
- docker_compose_kill.yaml
|
||||
- docker_compose_logs.yaml
|
||||
- docker_compose_ls.yaml
|
||||
- docker_compose_pause.yaml
|
||||
- docker_compose_port.yaml
|
||||
- docker_compose_ps.yaml
|
||||
- docker_compose_publish.yaml
|
||||
- docker_compose_pull.yaml
|
||||
- docker_compose_push.yaml
|
||||
- docker_compose_restart.yaml
|
||||
- docker_compose_rm.yaml
|
||||
- docker_compose_run.yaml
|
||||
- docker_compose_scale.yaml
|
||||
- docker_compose_start.yaml
|
||||
- docker_compose_stats.yaml
|
||||
- docker_compose_stop.yaml
|
||||
- docker_compose_top.yaml
|
||||
- docker_compose_unpause.yaml
|
||||
- docker_compose_up.yaml
|
||||
- docker_compose_version.yaml
|
||||
- docker_compose_volumes.yaml
|
||||
- docker_compose_wait.yaml
|
||||
- docker_compose_watch.yaml
|
||||
options:
|
||||
- option: all-resources
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Include all resources, even those not used by services
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: ansi
|
||||
value_type: string
|
||||
default_value: auto
|
||||
description: |
|
||||
Control when to print ANSI control characters ("never"|"always"|"auto")
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: compatibility
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Run compose in backward compatibility mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: env-file
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: Specify an alternate environment file
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: file
|
||||
shorthand: f
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: Compose configuration files
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: no-ansi
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Do not print ANSI control characters (DEPRECATED)
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: parallel
|
||||
value_type: int
|
||||
default_value: "-1"
|
||||
description: Control max parallelism, -1 for unlimited
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: profile
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: Specify a profile to enable
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: progress
|
||||
value_type: string
|
||||
description: Set type of progress output (auto, tty, plain, json, quiet)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: project-directory
|
||||
value_type: string
|
||||
description: |-
|
||||
Specify an alternate working directory
|
||||
(default: the path of the, first specified, Compose file)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: project-name
|
||||
shorthand: p
|
||||
value_type: string
|
||||
description: Project name
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: verbose
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Show more output
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: version
|
||||
shorthand: v
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Show the Docker Compose version information
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: workdir
|
||||
value_type: string
|
||||
description: |-
|
||||
DEPRECATED! USE --project-directory INSTEAD.
|
||||
Specify an alternate working directory
|
||||
(default: the path of the, first specified, Compose file)
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
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.
|
||||
Use the `-f` flag to specify the location of a Compose [configuration file](/reference/compose-file/).
|
||||
|
||||
#### Specifying multiple Compose files
|
||||
You can supply multiple `-f` configuration files. When you supply multiple files, Compose combines them into a single
|
||||
@ -15,10 +244,10 @@ long: |-
|
||||
For example, consider this command line:
|
||||
|
||||
```console
|
||||
$ docker compose -f docker-compose.yml -f docker-compose.admin.yml run backup_db
|
||||
$ docker compose -f compose.yaml -f compose.admin.yaml run backup_db
|
||||
```
|
||||
|
||||
The `docker-compose.yml` file might specify a `webapp` service.
|
||||
The `compose.yaml` file might specify a `webapp` service.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
@ -29,7 +258,7 @@ long: |-
|
||||
volumes:
|
||||
- "/data"
|
||||
```
|
||||
If the `docker-compose.admin.yml` also specifies this same service, any matching fields override the previous file.
|
||||
If the `compose.admin.yaml` also specifies this same service, any matching fields override the previous file.
|
||||
New values, add to the `webapp` service configuration.
|
||||
|
||||
```yaml
|
||||
@ -145,228 +374,6 @@ long: |-
|
||||
Next, the containers are created. The `db` service is started, and the `backend` and `proxy` wait until the `db` service is healthy before starting.
|
||||
|
||||
Dry Run mode works with almost all commands. You cannot use Dry Run mode with a command that doesn't change the state of a Compose stack such as `ps`, `ls`, `logs` for example.
|
||||
usage: docker compose
|
||||
pname: docker
|
||||
plink: docker.yaml
|
||||
cname:
|
||||
- docker compose attach
|
||||
- docker compose build
|
||||
- docker compose config
|
||||
- docker compose cp
|
||||
- docker compose create
|
||||
- docker compose down
|
||||
- docker compose events
|
||||
- docker compose exec
|
||||
- docker compose images
|
||||
- docker compose kill
|
||||
- docker compose logs
|
||||
- docker compose ls
|
||||
- docker compose pause
|
||||
- docker compose port
|
||||
- docker compose ps
|
||||
- docker compose pull
|
||||
- docker compose push
|
||||
- docker compose restart
|
||||
- docker compose rm
|
||||
- docker compose run
|
||||
- docker compose scale
|
||||
- docker compose start
|
||||
- docker compose stats
|
||||
- docker compose stop
|
||||
- docker compose top
|
||||
- docker compose unpause
|
||||
- docker compose up
|
||||
- docker compose version
|
||||
- docker compose wait
|
||||
- docker compose watch
|
||||
clink:
|
||||
- docker_compose_attach.yaml
|
||||
- docker_compose_build.yaml
|
||||
- docker_compose_config.yaml
|
||||
- docker_compose_cp.yaml
|
||||
- docker_compose_create.yaml
|
||||
- docker_compose_down.yaml
|
||||
- docker_compose_events.yaml
|
||||
- docker_compose_exec.yaml
|
||||
- docker_compose_images.yaml
|
||||
- docker_compose_kill.yaml
|
||||
- docker_compose_logs.yaml
|
||||
- docker_compose_ls.yaml
|
||||
- docker_compose_pause.yaml
|
||||
- docker_compose_port.yaml
|
||||
- docker_compose_ps.yaml
|
||||
- docker_compose_pull.yaml
|
||||
- docker_compose_push.yaml
|
||||
- docker_compose_restart.yaml
|
||||
- docker_compose_rm.yaml
|
||||
- docker_compose_run.yaml
|
||||
- docker_compose_scale.yaml
|
||||
- docker_compose_start.yaml
|
||||
- docker_compose_stats.yaml
|
||||
- docker_compose_stop.yaml
|
||||
- docker_compose_top.yaml
|
||||
- docker_compose_unpause.yaml
|
||||
- docker_compose_up.yaml
|
||||
- docker_compose_version.yaml
|
||||
- docker_compose_wait.yaml
|
||||
- docker_compose_watch.yaml
|
||||
options:
|
||||
- option: all-resources
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Include all resources, even those not used by services
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: ansi
|
||||
value_type: string
|
||||
default_value: auto
|
||||
description: |
|
||||
Control when to print ANSI control characters ("never"|"always"|"auto")
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: compatibility
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Run compose in backward compatibility mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: env-file
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: Specify an alternate environment file
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: file
|
||||
shorthand: f
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: Compose configuration files
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: no-ansi
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Do not print ANSI control characters (DEPRECATED)
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: parallel
|
||||
value_type: int
|
||||
default_value: "-1"
|
||||
description: Control max parallelism, -1 for unlimited
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: profile
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: Specify a profile to enable
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: progress
|
||||
value_type: string
|
||||
default_value: auto
|
||||
description: Set type of progress output (auto, tty, plain, json, quiet)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: project-directory
|
||||
value_type: string
|
||||
description: |-
|
||||
Specify an alternate working directory
|
||||
(default: the path of the, first specified, Compose file)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: project-name
|
||||
shorthand: p
|
||||
value_type: string
|
||||
description: Project name
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: verbose
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Show more output
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: version
|
||||
shorthand: v
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Show the Docker Compose version information
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: workdir
|
||||
value_type: string
|
||||
description: |-
|
||||
DEPRECATED! USE --project-directory INSTEAD.
|
||||
Specify an alternate working directory
|
||||
(default: the path of the, first specified, Compose file)
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
|
@ -4,9 +4,11 @@ long: Experimental commands
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
cname:
|
||||
- docker compose alpha generate
|
||||
- docker compose alpha publish
|
||||
- docker compose alpha viz
|
||||
clink:
|
||||
- docker_compose_alpha_generate.yaml
|
||||
- docker_compose_alpha_publish.yaml
|
||||
- docker_compose_alpha_viz.yaml
|
||||
inherited_options:
|
||||
|
53
docs/reference/docker_compose_alpha_generate.yaml
Normal file
53
docs/reference/docker_compose_alpha_generate.yaml
Normal file
@ -0,0 +1,53 @@
|
||||
command: docker compose alpha generate
|
||||
short: EXPERIMENTAL - Generate a Compose file from existing containers
|
||||
long: EXPERIMENTAL - Generate a Compose file from existing containers
|
||||
usage: docker compose alpha generate [OPTIONS] [CONTAINERS...]
|
||||
pname: docker compose alpha
|
||||
plink: docker_compose_alpha.yaml
|
||||
options:
|
||||
- option: format
|
||||
value_type: string
|
||||
default_value: yaml
|
||||
description: 'Format the output. Values: [yaml | json]'
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: name
|
||||
value_type: string
|
||||
description: Project name to set in the Compose file
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: project-dir
|
||||
value_type: string
|
||||
description: Directory to use for the project
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
@ -1,14 +1,14 @@
|
||||
command: docker compose alpha publish
|
||||
short: Publish compose application
|
||||
long: Publish compose application
|
||||
usage: docker compose alpha publish [OPTIONS] [REPOSITORY]
|
||||
usage: docker compose alpha publish [OPTIONS] REPOSITORY[:TAG]
|
||||
pname: docker compose alpha
|
||||
plink: docker_compose_alpha.yaml
|
||||
options:
|
||||
- option: oci-version
|
||||
value_type: string
|
||||
description: |
|
||||
OCI Image/Artifact specification version (automatically determined by default)
|
||||
OCI image/artifact specification version (automatically determined by default)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
@ -25,6 +25,27 @@ options:
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: with-env
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Include environment variables in the published OCI artifact
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: "yes"
|
||||
shorthand: "y"
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Assume "yes" as answer to all prompts
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
@ -37,7 +58,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
|
@ -69,7 +69,7 @@ inherited_options:
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
hidden: true
|
||||
experimental: false
|
||||
experimentalcli: true
|
||||
kubernetes: false
|
||||
|
29
docs/reference/docker_compose_bridge.yaml
Normal file
29
docs/reference/docker_compose_bridge.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
command: docker compose bridge
|
||||
short: Convert compose files into another model
|
||||
long: Convert compose files into another model
|
||||
pname: docker compose
|
||||
plink: docker_compose.yaml
|
||||
cname:
|
||||
- docker compose bridge convert
|
||||
- docker compose bridge transformations
|
||||
clink:
|
||||
- docker_compose_bridge_convert.yaml
|
||||
- docker_compose_bridge_transformations.yaml
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
59
docs/reference/docker_compose_bridge_convert.yaml
Normal file
59
docs/reference/docker_compose_bridge_convert.yaml
Normal file
@ -0,0 +1,59 @@
|
||||
command: docker compose bridge convert
|
||||
short: |
|
||||
Convert compose files to Kubernetes manifests, Helm charts, or another model
|
||||
long: |
|
||||
Convert compose files to Kubernetes manifests, Helm charts, or another model
|
||||
usage: docker compose bridge convert
|
||||
pname: docker compose bridge
|
||||
plink: docker_compose_bridge.yaml
|
||||
options:
|
||||
- option: output
|
||||
shorthand: o
|
||||
value_type: string
|
||||
default_value: out
|
||||
description: The output directory for the Kubernetes resources
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: templates
|
||||
value_type: string
|
||||
description: Directory containing transformation templates
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
- option: transformation
|
||||
shorthand: t
|
||||
value_type: stringArray
|
||||
default_value: '[]'
|
||||
description: |
|
||||
Transformation to apply to compose model (default: docker/compose-bridge-kubernetes)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
29
docs/reference/docker_compose_bridge_transformations.yaml
Normal file
29
docs/reference/docker_compose_bridge_transformations.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
command: docker compose bridge transformations
|
||||
short: Manage transformation images
|
||||
long: Manage transformation images
|
||||
pname: docker compose bridge
|
||||
plink: docker_compose_bridge.yaml
|
||||
cname:
|
||||
- docker compose bridge transformations create
|
||||
- docker compose bridge transformations list
|
||||
clink:
|
||||
- docker_compose_bridge_transformations_create.yaml
|
||||
- docker_compose_bridge_transformations_list.yaml
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
@ -0,0 +1,36 @@
|
||||
command: docker compose bridge transformations create
|
||||
short: Create a new transformation
|
||||
long: Create a new transformation
|
||||
usage: docker compose bridge transformations create [OPTION] PATH
|
||||
pname: docker compose bridge transformations
|
||||
plink: docker_compose_bridge_transformations.yaml
|
||||
options:
|
||||
- option: from
|
||||
shorthand: f
|
||||
value_type: string
|
||||
description: |
|
||||
Existing transformation to copy (default: docker/compose-bridge-kubernetes)
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
inherited_options:
|
||||
- option: dry-run
|
||||
value_type: bool
|
||||
default_value: "false"
|
||||
description: Execute command in dry run mode
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
deprecated: false
|
||||
hidden: false
|
||||
experimental: false
|
||||
experimentalcli: false
|
||||
kubernetes: false
|
||||
swarm: false
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user