mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-09-12 05:49:08 +08:00
Compare commits
619 Commits
v0.11.0-rc
...
v0.14.0-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df7a3db947 | ||
|
|
d294232cb5 | ||
|
|
0a7f5c4d94 | ||
|
|
5777d980b5 | ||
|
|
46cf94092c | ||
|
|
da3435ed3a | ||
|
|
3e90cc4b84 | ||
|
|
6418669e75 | ||
|
|
188495aa93 | ||
|
|
2e2f9f571f | ||
|
|
d2ac1f2d6e | ||
|
|
7e3acad9f4 | ||
|
|
e04637cf34 | ||
|
|
b9c5f9f1ee | ||
|
|
92ab188781 | ||
|
|
dd4d52407f | ||
|
|
7432b483ce | ||
|
|
6e3164dc6f | ||
|
|
2fdb1682f8 | ||
|
|
7f1eaa2a8a | ||
|
|
fbddc9ebea | ||
|
|
d347499112 | ||
|
|
b1fb67f44a | ||
|
|
a9575a872a | ||
|
|
60f48059a7 | ||
|
|
ffff87be03 | ||
|
|
0a3e5e5257 | ||
|
|
151b0de8f2 | ||
|
|
e40c630758 | ||
|
|
ea3338c3f3 | ||
|
|
744c055560 | ||
|
|
ca0b583f5a | ||
|
|
e7f2da9c4f | ||
|
|
d805c784f2 | ||
|
|
a2866b79e3 | ||
|
|
12e1f65eb3 | ||
|
|
0d6b3a9d1d | ||
|
|
4b3c3c8401 | ||
|
|
ccc314a823 | ||
|
|
dc4b4c36bd | ||
|
|
5c29e6e26e | ||
|
|
6a0d5b771f | ||
|
|
59cc10767e | ||
|
|
b61b29f603 | ||
|
|
7cfef05661 | ||
|
|
4d39259f8e | ||
|
|
15fd39ebec | ||
|
|
a7d59ae332 | ||
|
|
e18a2f6e58 | ||
|
|
38fbd9a85c | ||
|
|
84ddbc2b3b | ||
|
|
b4799f9d16 | ||
|
|
7cded6b33b | ||
|
|
1b36bd0c4a | ||
|
|
7dc5639216 | ||
|
|
858e347306 | ||
|
|
adb9bc86e5 | ||
|
|
ef2e30deba | ||
|
|
c690d460e8 | ||
|
|
35781a6c78 | ||
|
|
de5efcb03b | ||
|
|
5c89004bb6 | ||
|
|
8abef59087 | ||
|
|
4999908fbc | ||
|
|
4af0ed5159 | ||
|
|
a4a8846e46 | ||
|
|
520dc5968a | ||
|
|
324afe60ad | ||
|
|
c0c3a55fca | ||
|
|
2a30229916 | ||
|
|
ed76661b0d | ||
|
|
a0cce9b31e | ||
|
|
d410597f5a | ||
|
|
9016d85718 | ||
|
|
2565c74a89 | ||
|
|
eab5cccbb4 | ||
|
|
e2be765e7b | ||
|
|
276dd5150f | ||
|
|
5c69fa267f | ||
|
|
b240a00def | ||
|
|
a8af6fa013 | ||
|
|
7eb3dfbd22 | ||
|
|
4b24f66a10 | ||
|
|
8d5b967f2d | ||
|
|
8842e19869 | ||
|
|
a0ce8bec97 | ||
|
|
84d79df93b | ||
|
|
df4b13320d | ||
|
|
bb511110d6 | ||
|
|
47cf4a5dbe | ||
|
|
cfbed42fa7 | ||
|
|
ff27ab7e86 | ||
|
|
5655e5e2b6 | ||
|
|
4b516af1f6 | ||
|
|
b1490ed5ce | ||
|
|
ea830c9758 | ||
|
|
8f576e5790 | ||
|
|
4327ee73b1 | ||
|
|
70a28fed12 | ||
|
|
fc22d39d6d | ||
|
|
1cc5e39cb8 | ||
|
|
1815e4d9b2 | ||
|
|
2ec1dbd1b6 | ||
|
|
a6163470b7 | ||
|
|
3dfb102f82 | ||
|
|
253cbee5c7 | ||
|
|
c1dfa74b98 | ||
|
|
647491dd99 | ||
|
|
9a71895a48 | ||
|
|
abff444562 | ||
|
|
1d0b542b1b | ||
|
|
6c485a98be | ||
|
|
9ebfde4897 | ||
|
|
e4ee2ca1fd | ||
|
|
849456c198 | ||
|
|
9a2536dd0d | ||
|
|
a03263acf8 | ||
|
|
0c0dcb7c8c | ||
|
|
9bce433154 | ||
|
|
04f0fc5871 | ||
|
|
e7da2b0686 | ||
|
|
eab565afe7 | ||
|
|
7d952441ea | ||
|
|
835a6b1096 | ||
|
|
e273a53c88 | ||
|
|
dcdcce6c52 | ||
|
|
c5b4ce9e7b | ||
|
|
8f484f6ac1 | ||
|
|
b748185f48 | ||
|
|
a6228ed78f | ||
|
|
fcbe2803c8 | ||
|
|
83c30c6c5a | ||
|
|
8db86e4031 | ||
|
|
e705cafcd5 | ||
|
|
32f17b0de1 | ||
|
|
d40c4bb046 | ||
|
|
25f8011825 | ||
|
|
d0f9655aa2 | ||
|
|
ce9a486a0e | ||
|
|
85abcc413e | ||
|
|
e5acb010c9 | ||
|
|
79f50ad924 | ||
|
|
5723ceefb6 | ||
|
|
95185e9525 | ||
|
|
e423a67f7b | ||
|
|
545a5c97c6 | ||
|
|
625d90b983 | ||
|
|
9999fc63e8 | ||
|
|
303e509bbf | ||
|
|
ae0a5e495a | ||
|
|
2edb7a04a9 | ||
|
|
a0599c1c31 | ||
|
|
eedf9f10e8 | ||
|
|
d891634fc6 | ||
|
|
af75d0bd7d | ||
|
|
e008b846bb | ||
|
|
fd11d93381 | ||
|
|
aa518f9b88 | ||
|
|
b16bd02f95 | ||
|
|
69bd408964 | ||
|
|
d8e9c7f5b5 | ||
|
|
fd54daf184 | ||
|
|
9057bd27af | ||
|
|
5a466918f9 | ||
|
|
56fc68eb7e | ||
|
|
ccfcf4bc37 | ||
|
|
560eaf0e78 | ||
|
|
daaa8f2482 | ||
|
|
97052cf203 | ||
|
|
2eccaadce5 | ||
|
|
aa4317bfce | ||
|
|
953cbf6696 | ||
|
|
414f215929 | ||
|
|
698eb840a3 | ||
|
|
714b85aaaf | ||
|
|
fb604d4b57 | ||
|
|
73d8969158 | ||
|
|
64e2b2532a | ||
|
|
c2befc0c12 | ||
|
|
345551ae0d | ||
|
|
97e8fa7aaf | ||
|
|
cdfc35d0b6 | ||
|
|
ce66d8830d | ||
|
|
fe08cf2981 | ||
|
|
c9d1c41d20 | ||
|
|
bda968ad5d | ||
|
|
481384b185 | ||
|
|
67d9385ce0 | ||
|
|
598bc16e5d | ||
|
|
760244ee3e | ||
|
|
d0177c6da3 | ||
|
|
8f8ed68b61 | ||
|
|
981cc8c2aa | ||
|
|
9822409b67 | ||
|
|
328666dc6a | ||
|
|
42d2719b08 | ||
|
|
3b33ac48d2 | ||
|
|
e0303dd65a | ||
|
|
dab7af617a | ||
|
|
0326d2a5b1 | ||
|
|
b4c81a4d27 | ||
|
|
7b3c4fc714 | ||
|
|
43ed470208 | ||
|
|
089982153f | ||
|
|
7393650008 | ||
|
|
b36c5196dd | ||
|
|
1484862a50 | ||
|
|
e5c3fa5293 | ||
|
|
2c58e6003f | ||
|
|
30ae5ceb6e | ||
|
|
6ffb77dcda | ||
|
|
2c1f46450a | ||
|
|
052f279de7 | ||
|
|
89684021b3 | ||
|
|
95bdecc145 | ||
|
|
082d5d70b2 | ||
|
|
5b75930a6d | ||
|
|
e41ab8d10d | ||
|
|
4b408c79fe | ||
|
|
cff7baff1c | ||
|
|
5130700981 | ||
|
|
13beda8b11 | ||
|
|
8babd5a147 | ||
|
|
cb856682e9 | ||
|
|
c65b7ed24f | ||
|
|
2c3d7dab3f | ||
|
|
13467c1f5d | ||
|
|
d0c4bed484 | ||
|
|
dbaad32f49 | ||
|
|
528e3ba259 | ||
|
|
1ff261d38e | ||
|
|
bef5d567b0 | ||
|
|
da95d9f0ca | ||
|
|
7206e2d179 | ||
|
|
736094794c | ||
|
|
a399a97949 | ||
|
|
62a416fe12 | ||
|
|
f6564c3147 | ||
|
|
b49911416c | ||
|
|
22c2538466 | ||
|
|
1861405b1e | ||
|
|
c9aeca19ce | ||
|
|
59827f5c27 | ||
|
|
827622421e | ||
|
|
f0c5dfaf48 | ||
|
|
703c765ec8 | ||
|
|
fb2c62a038 | ||
|
|
eabbee797b | ||
|
|
7e4021a43d | ||
|
|
2478f300aa | ||
|
|
620c57c86c | ||
|
|
8bea1cb417 | ||
|
|
147c7135b0 | ||
|
|
650a7af0ae | ||
|
|
4f738020fd | ||
|
|
d852568a29 | ||
|
|
68c3ac4f66 | ||
|
|
38afdf1f52 | ||
|
|
b2e723e2a3 | ||
|
|
02c2073feb | ||
|
|
61dff684ad | ||
|
|
78adfc80a9 | ||
|
|
7c590ecb9a | ||
|
|
24e043e375 | ||
|
|
7094eb86c9 | ||
|
|
81ea718ea4 | ||
|
|
9060cab077 | ||
|
|
3cd6d8d6e4 | ||
|
|
ba43fe08f4 | ||
|
|
6b63e7e3de | ||
|
|
57d737a13c | ||
|
|
671347dc35 | ||
|
|
02bc4e8992 | ||
|
|
1cdefbe901 | ||
|
|
7694f0b9d8 | ||
|
|
fa9126c61f | ||
|
|
ebae070f7e | ||
|
|
617f538cb3 | ||
|
|
0f45b629ad | ||
|
|
ac5b3241b1 | ||
|
|
ee24a36c4f | ||
|
|
8484fcdd57 | ||
|
|
45deb29f09 | ||
|
|
6641167e7d | ||
|
|
9f4987997c | ||
|
|
8337c25fa4 | ||
|
|
6b048e2316 | ||
|
|
54a1f0f0ea | ||
|
|
57dc45774a | ||
|
|
9d8ac1ce2d | ||
|
|
0a0252d9b3 | ||
|
|
c6535e9675 | ||
|
|
d762c76a68 | ||
|
|
1091707bd5 | ||
|
|
a4c392f4db | ||
|
|
e4880c5dd1 | ||
|
|
b2510c6b94 | ||
|
|
5b5c4c8c9d | ||
|
|
ceb5bc807c | ||
|
|
b2f705ad71 | ||
|
|
6028094e6b | ||
|
|
9516ce8e25 | ||
|
|
d82637582c | ||
|
|
1e80c70990 | ||
|
|
54032316f9 | ||
|
|
aac7a47469 | ||
|
|
aa0aeac297 | ||
|
|
cec4496d3b | ||
|
|
9368ecb67e | ||
|
|
20c947990c | ||
|
|
eeeff1cf23 | ||
|
|
752680e289 | ||
|
|
5bf02d9f7b | ||
|
|
0962fdbb04 | ||
|
|
1f5562315b | ||
|
|
a102d33738 | ||
|
|
940e0a4a3c | ||
|
|
a978b2b7a3 | ||
|
|
1326634c7d | ||
|
|
7a724ac445 | ||
|
|
55e164a540 | ||
|
|
707ae87060 | ||
|
|
cb37886658 | ||
|
|
c855277d53 | ||
|
|
898a8eeddf | ||
|
|
c857eaa380 | ||
|
|
55db25c21c | ||
|
|
f353814390 | ||
|
|
271a467612 | ||
|
|
b3b8c62ad4 | ||
|
|
eacf2bdf3d | ||
|
|
d537b9e418 | ||
|
|
616fb3e55c | ||
|
|
da5f853b44 | ||
|
|
4932eecc3f | ||
|
|
7f93616ff1 | ||
|
|
ab58333311 | ||
|
|
9efaa2793d | ||
|
|
80aa28f75c | ||
|
|
8819ac1b65 | ||
|
|
0408f3ac45 | ||
|
|
7683ef9137 | ||
|
|
3f423468df | ||
|
|
ff8bca206b | ||
|
|
08a70ecdcc | ||
|
|
d83da63320 | ||
|
|
639e0bc5ed | ||
|
|
d0a9a81e2e | ||
|
|
de1a560f07 | ||
|
|
e168fd826c | ||
|
|
2f1b7a0131 | ||
|
|
f3871b158f | ||
|
|
deb9dbe9bb | ||
|
|
6f71ea8904 | ||
|
|
e437f7ba04 | ||
|
|
abfc04f621 | ||
|
|
612dfdd813 | ||
|
|
ee19ce5ef2 | ||
|
|
23c2498dee | ||
|
|
390eedc50b | ||
|
|
adc839aa40 | ||
|
|
7838ade9f3 | ||
|
|
c043c9229e | ||
|
|
05a0fdf744 | ||
|
|
dfb557b34f | ||
|
|
d0d8bfbca4 | ||
|
|
21e4eb89b2 | ||
|
|
14834e6085 | ||
|
|
267e30a19c | ||
|
|
be4fd7110d | ||
|
|
24668122d9 | ||
|
|
31d021a9ca | ||
|
|
7497e6481e | ||
|
|
de9d253f45 | ||
|
|
f4f511201b | ||
|
|
beca8b6adf | ||
|
|
457dc402d3 | ||
|
|
34b9a629a0 | ||
|
|
ad674e2666 | ||
|
|
503d483731 | ||
|
|
6e5aefbb98 | ||
|
|
7d2c9d5ef5 | ||
|
|
1734abbb76 | ||
|
|
b06a55cf53 | ||
|
|
38137b29dd | ||
|
|
fc7144f61d | ||
|
|
ac93a7fbfb | ||
|
|
48f9b86b9a | ||
|
|
6c32a8c4c1 | ||
|
|
7a08248c4e | ||
|
|
05af608774 | ||
|
|
511e41386f | ||
|
|
fd251d2a7b | ||
|
|
5836c24e7d | ||
|
|
c8f8a106ed | ||
|
|
198764f116 | ||
|
|
0dd89f6029 | ||
|
|
8da8ee2aea | ||
|
|
6db8569f09 | ||
|
|
5a0e4c1023 | ||
|
|
ded91da575 | ||
|
|
508b2ef0c6 | ||
|
|
05b8821625 | ||
|
|
01245e72ab | ||
|
|
22e9e3342b | ||
|
|
0e3911147a | ||
|
|
2aa6d52b06 | ||
|
|
561a4330cf | ||
|
|
7b4bc4f00a | ||
|
|
a012e0043b | ||
|
|
2c2294fa43 | ||
|
|
197824c6f2 | ||
|
|
22e61ef06f | ||
|
|
159eac42f3 | ||
|
|
6c77b76b7b | ||
|
|
130e9fe093 | ||
|
|
e9fb769c60 | ||
|
|
3dcb03452c | ||
|
|
9b7d30c9a0 | ||
|
|
2134a1e104 | ||
|
|
cc6957d1cc | ||
|
|
0878d5b22b | ||
|
|
c8002e58a4 | ||
|
|
cfcd1d9420 | ||
|
|
e6756d951a | ||
|
|
b9aad03e7a | ||
|
|
0bd6f3c7f5 | ||
|
|
e2ebab5f26 | ||
|
|
e018f8b6fb | ||
|
|
03bedfb3c3 | ||
|
|
bdaaca40a2 | ||
|
|
bc021c89a8 | ||
|
|
798402314c | ||
|
|
7cfb440136 | ||
|
|
80358842c4 | ||
|
|
77aedb751e | ||
|
|
739ec964db | ||
|
|
320a3109f3 | ||
|
|
2c986bc184 | ||
|
|
6c31f43cc9 | ||
|
|
7b049b99c5 | ||
|
|
bf5a70023c | ||
|
|
8d001e338f | ||
|
|
73ea0826ca | ||
|
|
66e6dab26b | ||
|
|
0138f2a00f | ||
|
|
a59058e8a5 | ||
|
|
f6b7a3c522 | ||
|
|
8fe2070d10 | ||
|
|
54bb799d15 | ||
|
|
957044825f | ||
|
|
42a0f3d504 | ||
|
|
84ad208985 | ||
|
|
3631dc17c9 | ||
|
|
bafdc63b8c | ||
|
|
51c94cd2a6 | ||
|
|
31d88398bc | ||
|
|
fbf6594758 | ||
|
|
f54a67de6d | ||
|
|
f35b2b7cab | ||
|
|
29ba5ecef6 | ||
|
|
fb50d82fd8 | ||
|
|
87e8e4b847 | ||
|
|
a71a24c0f4 | ||
|
|
76119b0f61 | ||
|
|
7843b5f417 | ||
|
|
da6662975f | ||
|
|
de4dbb7d00 | ||
|
|
3bd4bca994 | ||
|
|
296832c90e | ||
|
|
56d55a4137 | ||
|
|
626e6f8fa3 | ||
|
|
5941bf0494 | ||
|
|
29a496cdab | ||
|
|
a43d9a67c7 | ||
|
|
c47eb3bf5a | ||
|
|
a97e1641a4 | ||
|
|
86ae8ea854 | ||
|
|
d37d483097 | ||
|
|
4e96faa201 | ||
|
|
e5419ef6d7 | ||
|
|
14747a490a | ||
|
|
e5cee892ed | ||
|
|
ef4b984df4 | ||
|
|
a8f402e28d | ||
|
|
2eba99b40b | ||
|
|
7686fa1f16 | ||
|
|
51b9bab245 | ||
|
|
6b5758f4cd | ||
|
|
bd375a14a8 | ||
|
|
b01693f63e | ||
|
|
4a059d5144 | ||
|
|
f3775c0046 | ||
|
|
50fbdd86f9 | ||
|
|
1f61de0fcc | ||
|
|
e206c585bb | ||
|
|
5e46d8057d | ||
|
|
4e7709e54c | ||
|
|
5ed8f1b7d9 | ||
|
|
1d12c1f5b3 | ||
|
|
3ef93e081c | ||
|
|
18894a8e3a | ||
|
|
13ec635988 | ||
|
|
f804b8fa4b | ||
|
|
21a55ff9a1 | ||
|
|
dd350284df | ||
|
|
c010d3de8d | ||
|
|
d11dbbf9f7 | ||
|
|
75cdceb9f1 | ||
|
|
10ff93f190 | ||
|
|
bf00185809 | ||
|
|
90f03e57c2 | ||
|
|
a59fd3ebfe | ||
|
|
3eb490153d | ||
|
|
d957d8b987 | ||
|
|
5a1f252bd9 | ||
|
|
ab4585f38c | ||
|
|
3003045c0b | ||
|
|
a6f3f290b4 | ||
|
|
27d072a099 | ||
|
|
8e3df1943c | ||
|
|
8c54de66ce | ||
|
|
06b9ac2dc4 | ||
|
|
b8739d7441 | ||
|
|
23fe02993b | ||
|
|
1d177f00d2 | ||
|
|
ceaba7011f | ||
|
|
9c06f383ba | ||
|
|
e11c5e3e96 | ||
|
|
f5719f3017 | ||
|
|
163babdca7 | ||
|
|
094d1aded8 | ||
|
|
05ef20b434 | ||
|
|
cc718b3444 | ||
|
|
e98e8f6ac9 | ||
|
|
36541ed9d5 | ||
|
|
418ea82d3a | ||
|
|
130bbda00e | ||
|
|
2666bd6996 | ||
|
|
ff2c8da803 | ||
|
|
e094296f37 | ||
|
|
7c3b77fb36 | ||
|
|
fb4c4f07ca | ||
|
|
b9e25e82cf | ||
|
|
089036da29 | ||
|
|
1123bfed10 | ||
|
|
7f2293308b | ||
|
|
a65131f9d3 | ||
|
|
8a3a646c61 | ||
|
|
4384947be1 | ||
|
|
69421182ca | ||
|
|
068382f5df | ||
|
|
c4bec05466 | ||
|
|
89e1ac0a6e | ||
|
|
b84e0e11b4 | ||
|
|
d95f5f8f3b | ||
|
|
b4c0941683 | ||
|
|
cf9798cede | ||
|
|
20d2501edc | ||
|
|
d45601fdc6 | ||
|
|
c81a9a89cf | ||
|
|
87b9f9ecfb | ||
|
|
cbc473359a | ||
|
|
2eba60db75 | ||
|
|
0dcbed3f53 | ||
|
|
ca08eb65e2 | ||
|
|
6f37d9bee7 | ||
|
|
e65f6b8c8b | ||
|
|
707dc43d55 | ||
|
|
8cbb7a9319 | ||
|
|
4f5a56aadb | ||
|
|
399beb53d9 | ||
|
|
7dec9fd6e7 | ||
|
|
120f3a8918 | ||
|
|
bd672eaf5b | ||
|
|
c2500ea2d8 | ||
|
|
a4663b4b2e | ||
|
|
57c618b83a | ||
|
|
b3a4f95110 | ||
|
|
28a1eb3527 | ||
|
|
75ecc15958 | ||
|
|
2235ebce2f | ||
|
|
7147463418 | ||
|
|
010e4c8d54 | ||
|
|
6f394a0691 | ||
|
|
efd7279118 | ||
|
|
601056f3a7 | ||
|
|
0a7f96cbfb | ||
|
|
1c530c2fe0 | ||
|
|
1e576dd7c6 | ||
|
|
7a5472153b | ||
|
|
b986ce566b | ||
|
|
daba16f4be | ||
|
|
ee36e2264e | ||
|
|
329e98d9f0 | ||
|
|
f4513f7028 | ||
|
|
b1c5449428 | ||
|
|
431732f5d1 | ||
|
|
687feca9e8 | ||
|
|
d4a2c8d0c3 | ||
|
|
bef42b2441 | ||
|
|
2de333fdd3 | ||
|
|
1138789f20 | ||
|
|
1f4ac09ffb | ||
|
|
26a8ffb393 | ||
|
|
9b7aada99b | ||
|
|
fd6207695b | ||
|
|
def96d2bf4 | ||
|
|
f5f00e68ef | ||
|
|
14aebe713e | ||
|
|
9d2388e6f5 | ||
|
|
75e2c46295 | ||
|
|
2c02db8db4 | ||
|
|
e304a05d2a | ||
|
|
14c1ea0e11 | ||
|
|
c30bcade2c | ||
|
|
62bfb19db4 | ||
|
|
d0bff18cee | ||
|
|
8ad30d0a35 |
5
.github/dependabot.yml
vendored
5
.github/dependabot.yml
vendored
@@ -5,6 +5,11 @@ updates:
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
ignore:
|
||||
# ignore this dependency
|
||||
# it seems a bug with dependabot as pining to commit sha should not
|
||||
# trigger a new version: https://github.com/docker/buildx/pull/2222#issuecomment-1919092153
|
||||
- dependency-name: "docker/docs"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "bot"
|
||||
|
||||
245
.github/workflows/build.yml
vendored
245
.github/workflows/build.yml
vendored
@@ -24,107 +24,193 @@ env:
|
||||
REPO_SLUG: "docker/buildx-bin"
|
||||
DESTDIR: "./bin"
|
||||
TEST_CACHE_SCOPE: "test"
|
||||
TESTFLAGS: "-v --parallel=6 --timeout=30m"
|
||||
GOTESTSUM_FORMAT: "standard-verbose"
|
||||
GO_VERSION: "1.21"
|
||||
GOTESTSUM_VERSION: "v1.9.0" # same as one in Dockerfile
|
||||
|
||||
jobs:
|
||||
prepare-test:
|
||||
test-integration:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: ${{ env.BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v3
|
||||
with:
|
||||
targets: integration-test-base
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
|
||||
*.cache-to=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- prepare-test
|
||||
env:
|
||||
TESTFLAGS: "-v --parallel=6 --timeout=30m"
|
||||
TESTFLAGS_DOCKER: "-v --parallel=1 --timeout=30m"
|
||||
GOTESTSUM_FORMAT: "standard-verbose"
|
||||
TEST_IMAGE_BUILD: "0"
|
||||
TEST_IMAGE_ID: "buildx-tests"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
buildkit:
|
||||
- master
|
||||
- latest
|
||||
- buildx-stable-1
|
||||
- v0.13.1
|
||||
- v0.12.5
|
||||
- v0.11.6
|
||||
worker:
|
||||
- docker
|
||||
- docker-container
|
||||
- remote
|
||||
pkg:
|
||||
- ./tests
|
||||
mode:
|
||||
- ""
|
||||
- experimental
|
||||
include:
|
||||
- pkg: ./...
|
||||
skip-integration-tests: 1
|
||||
- worker: docker
|
||||
pkg: ./tests
|
||||
- worker: docker+containerd # same as docker, but with containerd snapshotter
|
||||
pkg: ./tests
|
||||
- worker: docker
|
||||
pkg: ./tests
|
||||
mode: experimental
|
||||
- worker: docker+containerd # same as docker, but with containerd snapshotter
|
||||
pkg: ./tests
|
||||
mode: experimental
|
||||
steps:
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
echo "TESTREPORTS_NAME=${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.buildkit }}-${{ matrix.worker }}-${{ matrix.mode }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||
if [ -n "${{ matrix.buildkit }}" ]; then
|
||||
echo "TEST_BUILDKIT_TAG=${{ matrix.buildkit }}" >> $GITHUB_ENV
|
||||
fi
|
||||
testFlags="--run=//worker=$(echo "${{ matrix.worker }}" | sed 's/\+/\\+/g')$"
|
||||
case "${{ matrix.worker }}" in
|
||||
docker | docker+containerd)
|
||||
echo "TESTFLAGS=${{ env.TESTFLAGS_DOCKER }} $testFlags" >> $GITHUB_ENV
|
||||
;;
|
||||
*)
|
||||
echo "TESTFLAGS=${{ env.TESTFLAGS }} $testFlags" >> $GITHUB_ENV
|
||||
;;
|
||||
esac
|
||||
if [[ "${{ matrix.worker }}" == "docker"* ]]; then
|
||||
echo "TEST_DOCKERD=1" >> $GITHUB_ENV
|
||||
fi
|
||||
if [ "${{ matrix.mode }}" = "experimental" ]; then
|
||||
echo "TEST_BUILDX_EXPERIMENTAL=1" >> $GITHUB_ENV
|
||||
fi
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
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
|
||||
with:
|
||||
version: ${{ env.BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.BUILDKIT_IMAGE }}
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
name: Build test image
|
||||
uses: docker/bake-action@v3
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: integration-test
|
||||
set: |
|
||||
*.cache-from=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
|
||||
*.output=type=docker,name=${{ env.TEST_IMAGE_ID }}
|
||||
-
|
||||
name: Test
|
||||
run: |
|
||||
export TEST_REPORT_SUFFIX=-${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.skip-integration-tests }}-${{ matrix.worker }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]')
|
||||
./hack/test
|
||||
env:
|
||||
TEST_DOCKERD: "${{ (matrix.worker == 'docker' || matrix.worker == 'docker-container') && '1' || '0' }}"
|
||||
TESTFLAGS: "${{ (matrix.worker == 'docker' || matrix.worker == 'docker-container') && env.TESTFLAGS_DOCKER || env.TESTFLAGS }} --run=//worker=${{ matrix.worker }}$"
|
||||
TEST_REPORT_SUFFIX: "-${{ env.TESTREPORTS_NAME }}"
|
||||
TESTPKGS: "${{ matrix.pkg }}"
|
||||
SKIP_INTEGRATION_TESTS: "${{ matrix.skip-integration-tests }}"
|
||||
-
|
||||
name: Send to Codecov
|
||||
if: always()
|
||||
uses: codecov/codecov-action@v3
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
directory: ./bin/testreports
|
||||
flags: integration
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
-
|
||||
name: Generate annotations
|
||||
if: always()
|
||||
uses: crazy-max/.github/.github/actions/gotest-annotations@1a64ea6d01db9a48aa61954cb20e265782c167d9
|
||||
uses: crazy-max/.github/.github/actions/gotest-annotations@fa6141aedf23596fb8bdcceab9cce8dadaa31bd9
|
||||
with:
|
||||
directory: ./bin/testreports
|
||||
-
|
||||
name: Upload test reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports
|
||||
name: test-reports-${{ env.TESTREPORTS_NAME }}
|
||||
path: ./bin/testreports
|
||||
|
||||
test-unit:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os:
|
||||
- ubuntu-22.04
|
||||
- macos-12
|
||||
- windows-2022
|
||||
env:
|
||||
SKIP_INTEGRATION_TESTS: 1
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "${{ env.GO_VERSION }}"
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
testreportsName=${{ github.job }}--${{ matrix.os }}
|
||||
testreportsBaseDir=./bin/testreports
|
||||
testreportsDir=$testreportsBaseDir/$testreportsName
|
||||
echo "TESTREPORTS_NAME=$testreportsName" >> $GITHUB_ENV
|
||||
echo "TESTREPORTS_BASEDIR=$testreportsBaseDir" >> $GITHUB_ENV
|
||||
echo "TESTREPORTS_DIR=$testreportsDir" >> $GITHUB_ENV
|
||||
mkdir -p $testreportsDir
|
||||
shell: bash
|
||||
-
|
||||
name: Install gotestsum
|
||||
run: |
|
||||
go install gotest.tools/gotestsum@${{ env.GOTESTSUM_VERSION }}
|
||||
-
|
||||
name: Test
|
||||
env:
|
||||
TMPDIR: ${{ runner.temp }}
|
||||
run: |
|
||||
gotestsum \
|
||||
--jsonfile="${{ env.TESTREPORTS_DIR }}/go-test-report.json" \
|
||||
--junitfile="${{ env.TESTREPORTS_DIR }}/junit-report.xml" \
|
||||
--packages="./..." \
|
||||
-- \
|
||||
"-mod=vendor" \
|
||||
"-coverprofile" "${{ env.TESTREPORTS_DIR }}/coverage.txt" \
|
||||
"-covermode" "atomic" ${{ env.TESTFLAGS }}
|
||||
shell: bash
|
||||
-
|
||||
name: Send to Codecov
|
||||
if: always()
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
directory: ${{ env.TESTREPORTS_DIR }}
|
||||
env_vars: RUNNER_OS
|
||||
flags: unit
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
-
|
||||
name: Generate annotations
|
||||
if: always()
|
||||
uses: crazy-max/.github/.github/actions/gotest-annotations@fa6141aedf23596fb8bdcceab9cce8dadaa31bd9
|
||||
with:
|
||||
directory: ${{ env.TESTREPORTS_DIR }}
|
||||
-
|
||||
name: Upload test reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-reports-${{ env.TESTREPORTS_NAME }}
|
||||
path: ${{ env.TESTREPORTS_BASEDIR }}
|
||||
|
||||
prepare-binaries:
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
@@ -132,7 +218,7 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Create matrix
|
||||
id: platforms
|
||||
@@ -159,13 +245,13 @@ jobs:
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.BUILDKIT_IMAGE }}
|
||||
@@ -180,27 +266,28 @@ jobs:
|
||||
CACHE_TO: type=gha,scope=binaries-${{ env.PLATFORM_PAIR }},mode=max
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: buildx
|
||||
name: buildx-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ env.DESTDIR }}/*
|
||||
if-no-files-found: error
|
||||
|
||||
bin-image:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- test
|
||||
- test-integration
|
||||
- test-unit
|
||||
if: ${{ github.event_name != 'pull_request' && github.repository == 'docker/buildx' }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: ${{ env.BUILDX_VERSION }}
|
||||
driver-opts: image=${{ env.BUILDKIT_IMAGE }}
|
||||
@@ -208,7 +295,7 @@ jobs:
|
||||
-
|
||||
name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REPO_SLUG }}
|
||||
@@ -220,13 +307,13 @@ jobs:
|
||||
-
|
||||
name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
username: ${{ vars.DOCKERPUBLICBOT_USERNAME }}
|
||||
password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }}
|
||||
-
|
||||
name: Build and push image
|
||||
uses: docker/bake-action@v3
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
@@ -241,18 +328,20 @@ jobs:
|
||||
release:
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- test
|
||||
- test-integration
|
||||
- test-unit
|
||||
- binaries
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Download binaries
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: buildx
|
||||
path: ${{ env.DESTDIR }}
|
||||
pattern: buildx-*
|
||||
merge-multiple: true
|
||||
-
|
||||
name: Create checksums
|
||||
run: ./hack/hash-files
|
||||
@@ -267,33 +356,9 @@ jobs:
|
||||
-
|
||||
name: GitHub Release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
|
||||
uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 # v2.0.4
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
draft: true
|
||||
files: ${{ env.DESTDIR }}/*
|
||||
|
||||
buildkit-edge:
|
||||
runs-on: ubuntu-22.04
|
||||
continue-on-error: true
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
version: ${{ env.BUILDX_VERSION }}
|
||||
driver-opts: image=moby/buildkit:master
|
||||
buildkitd-flags: --debug
|
||||
-
|
||||
# Just run a bake target to check eveything runs fine
|
||||
name: Build
|
||||
uses: docker/bake-action@v3
|
||||
with:
|
||||
targets: binaries
|
||||
|
||||
42
.github/workflows/codeql.yml
vendored
Normal file
42
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
name: codeql
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
- 'v[0-9]*'
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.21"
|
||||
|
||||
jobs:
|
||||
codeql:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ env.GO_VERSION }}
|
||||
-
|
||||
name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: go
|
||||
-
|
||||
name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
-
|
||||
name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:go"
|
||||
45
.github/workflows/docs-release.yml
vendored
45
.github/workflows/docs-release.yml
vendored
@@ -1,6 +1,11 @@
|
||||
name: docs-release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Git tag'
|
||||
required: true
|
||||
release:
|
||||
types:
|
||||
- released
|
||||
@@ -8,11 +13,11 @@ on:
|
||||
jobs:
|
||||
open-pr:
|
||||
runs-on: ubuntu-22.04
|
||||
if: ${{ github.event.release.prerelease != true && github.repository == 'docker/buildx' }}
|
||||
if: ${{ (github.event.release.prerelease != true || github.event.inputs.tag != '') && github.repository == 'docker/buildx' }}
|
||||
steps:
|
||||
-
|
||||
name: Checkout docs repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||
repository: docker/docs
|
||||
@@ -20,39 +25,47 @@ jobs:
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
rm -rf ./_data/buildx/*
|
||||
rm -rf ./data/buildx/*
|
||||
if [ -n "${{ github.event.inputs.tag }}" ]; then
|
||||
echo "RELEASE_NAME=${{ github.event.inputs.tag }}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "RELEASE_NAME=${{ github.event.release.name }}" >> $GITHUB_ENV
|
||||
fi
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
-
|
||||
name: Build docs
|
||||
uses: docker/bake-action@v3
|
||||
name: Generate yaml
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
source: ${{ github.server_url }}/${{ github.repository }}.git#${{ github.event.release.name }}
|
||||
source: ${{ github.server_url }}/${{ github.repository }}.git#${{ env.RELEASE_NAME }}
|
||||
targets: update-docs
|
||||
provenance: false
|
||||
set: |
|
||||
*.output=/tmp/buildx-docs
|
||||
env:
|
||||
DOCS_FORMATS: yaml
|
||||
-
|
||||
name: Copy files
|
||||
name: Copy yaml
|
||||
run: |
|
||||
cp /tmp/buildx-docs/out/reference/*.yaml ./_data/buildx/
|
||||
cp /tmp/buildx-docs/out/reference/*.yaml ./data/buildx/
|
||||
-
|
||||
name: Commit changes
|
||||
name: Update vendor
|
||||
run: |
|
||||
git add -A .
|
||||
make vendor
|
||||
env:
|
||||
VENDOR_MODULE: github.com/docker/buildx@${{ env.RELEASE_NAME }}
|
||||
-
|
||||
name: Create PR on docs repo
|
||||
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666
|
||||
uses: peter-evans/create-pull-request@70a41aba780001da0a30141984ae2a0c95d8704e # v6.0.2
|
||||
with:
|
||||
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||
push-to-fork: docker-tools-robot/docker.github.io
|
||||
commit-message: "build: update buildx reference to ${{ github.event.release.name }}"
|
||||
commit-message: "vendor: github.com/docker/buildx ${{ env.RELEASE_NAME }}"
|
||||
signoff: true
|
||||
branch: dispatch/buildx-ref-${{ github.event.release.name }}
|
||||
branch: dispatch/buildx-ref-${{ env.RELEASE_NAME }}
|
||||
delete-branch: true
|
||||
title: Update buildx reference to ${{ github.event.release.name }}
|
||||
title: Update buildx reference to ${{ env.RELEASE_NAME }}
|
||||
body: |
|
||||
Update the buildx reference documentation to keep in sync with the latest release `${{ github.event.release.name }}`
|
||||
Update the buildx reference documentation to keep in sync with the latest release `${{ env.RELEASE_NAME }}`
|
||||
draft: false
|
||||
|
||||
15
.github/workflows/docs-upstream.yml
vendored
15
.github/workflows/docs-upstream.yml
vendored
@@ -26,17 +26,18 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
-
|
||||
name: Build reference YAML docs
|
||||
uses: docker/bake-action@v3
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: update-docs
|
||||
provenance: false
|
||||
set: |
|
||||
*.output=/tmp/buildx-docs
|
||||
*.cache-from=type=gha,scope=docs-yaml
|
||||
@@ -45,18 +46,18 @@ jobs:
|
||||
DOCS_FORMATS: yaml
|
||||
-
|
||||
name: Upload reference YAML docs
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: docs-yaml
|
||||
path: /tmp/buildx-docs/out/reference
|
||||
retention-days: 1
|
||||
|
||||
validate:
|
||||
uses: docker/docs/.github/workflows/validate-upstream.yml@main
|
||||
uses: docker/docs/.github/workflows/validate-upstream.yml@6b73b05acb21edf7995cc5b3c6672d8e314cee7a # pin for artifact v4 support: https://github.com/docker/docs/pull/19220
|
||||
needs:
|
||||
- docs-yaml
|
||||
with:
|
||||
repo: https://github.com/${{ github.repository }}
|
||||
module-name: docker/buildx
|
||||
data-files-id: docs-yaml
|
||||
data-files-folder: buildx
|
||||
data-files-placeholder-folder: engine/reference/commandline
|
||||
create-placeholder-stubs: true
|
||||
|
||||
21
.github/workflows/e2e.yml
vendored
21
.github/workflows/e2e.yml
vendored
@@ -25,15 +25,15 @@ jobs:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
-
|
||||
name: Build
|
||||
uses: docker/bake-action@v3
|
||||
uses: docker/bake-action@v4
|
||||
with:
|
||||
targets: binaries
|
||||
set: |
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
mv ${{ env.DESTDIR }}/build/buildx ${{ env.DESTDIR }}/build/docker-buildx
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: binary
|
||||
path: ${{ env.DESTDIR }}/build
|
||||
@@ -82,6 +82,8 @@ jobs:
|
||||
driver-opt: qemu.install=true
|
||||
- driver: remote
|
||||
endpoint: tcp://localhost:1234
|
||||
- driver: docker-container
|
||||
metadata-provenance: max
|
||||
exclude:
|
||||
- driver: docker
|
||||
multi-node: mnode-true
|
||||
@@ -96,14 +98,14 @@ jobs:
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
uses: docker/setup-qemu-action@v3
|
||||
if: matrix.driver == 'docker' || matrix.driver == 'docker-container'
|
||||
-
|
||||
name: Install buildx
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: binary
|
||||
path: /home/runner/.docker/cli-plugins
|
||||
@@ -129,10 +131,13 @@ jobs:
|
||||
else
|
||||
echo "MULTI_NODE=0" >> $GITHUB_ENV
|
||||
fi
|
||||
if [ -n "${{ matrix.metadata-provenance }}" ]; then
|
||||
echo "BUILDX_METADATA_PROVENANCE=${{ matrix.metadata-provenance }}" >> $GITHUB_ENV
|
||||
fi
|
||||
-
|
||||
name: Install k3s
|
||||
if: matrix.driver == 'kubernetes'
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
10
.github/workflows/validate.yml
vendored
10
.github/workflows/validate.yml
vendored
@@ -28,12 +28,18 @@ jobs:
|
||||
- validate-docs
|
||||
- validate-generated-files
|
||||
steps:
|
||||
-
|
||||
name: Prepare
|
||||
run: |
|
||||
if [ "$GITHUB_REPOSITORY" = "docker/buildx" ]; then
|
||||
echo "GOLANGCI_LINT_MULTIPLATFORM=1" >> $GITHUB_ENV
|
||||
fi
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
version: latest
|
||||
-
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
run:
|
||||
timeout: 10m
|
||||
timeout: 30m
|
||||
skip-files:
|
||||
- ".*\\.pb\\.go$"
|
||||
|
||||
@@ -26,12 +26,13 @@ linters:
|
||||
|
||||
linters-settings:
|
||||
depguard:
|
||||
list-type: blacklist
|
||||
include-go-root: true
|
||||
packages:
|
||||
# The io/ioutil package has been deprecated.
|
||||
# https://go.dev/doc/go1.16#ioutil
|
||||
- io/ioutil
|
||||
rules:
|
||||
main:
|
||||
deny:
|
||||
# The io/ioutil package has been deprecated.
|
||||
# https://go.dev/doc/go1.16#ioutil
|
||||
- pkg: "io/ioutil"
|
||||
desc: The io/ioutil package has been deprecated.
|
||||
forbidigo:
|
||||
forbid:
|
||||
- '^fmt\.Errorf(# use errors\.Errorf instead)?$'
|
||||
@@ -47,3 +48,22 @@ issues:
|
||||
- linters:
|
||||
- revive
|
||||
text: "stutters"
|
||||
- linters:
|
||||
- revive
|
||||
text: "empty-block"
|
||||
- linters:
|
||||
- revive
|
||||
text: "superfluous-else"
|
||||
- linters:
|
||||
- revive
|
||||
text: "unused-parameter"
|
||||
- linters:
|
||||
- revive
|
||||
text: "redefines-builtin-id"
|
||||
- linters:
|
||||
- revive
|
||||
text: "if-return"
|
||||
|
||||
# show all
|
||||
max-issues-per-linter: 0
|
||||
max-same-issues: 0
|
||||
|
||||
41
Dockerfile
41
Dockerfile
@@ -1,18 +1,22 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
ARG GO_VERSION=1.20
|
||||
ARG XX_VERSION=1.2.1
|
||||
ARG DOCKERD_VERSION=20.10.14
|
||||
ARG GO_VERSION=1.21
|
||||
ARG XX_VERSION=1.4.0
|
||||
|
||||
# for testing
|
||||
ARG DOCKER_VERSION=26.0.0
|
||||
ARG GOTESTSUM_VERSION=v1.9.0
|
||||
ARG REGISTRY_VERSION=2.8.0
|
||||
ARG BUILDKIT_VERSION=v0.11.6
|
||||
ARG BUILDKIT_VERSION=v0.13.1
|
||||
ARG UNDOCK_VERSION=0.7.0
|
||||
|
||||
FROM docker:$DOCKERD_VERSION AS dockerd-release
|
||||
|
||||
# xx is a helper for cross-compilation
|
||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||
|
||||
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS golatest
|
||||
FROM moby/moby-bin:$DOCKER_VERSION AS docker-engine
|
||||
FROM dockereng/cli-bin:$DOCKER_VERSION AS docker-cli
|
||||
FROM registry:$REGISTRY_VERSION AS registry
|
||||
FROM moby/buildkit:$BUILDKIT_VERSION AS buildkit
|
||||
FROM crazymax/undock:$UNDOCK_VERSION AS undock
|
||||
|
||||
FROM golatest AS gobase
|
||||
COPY --from=xx / /
|
||||
@@ -21,10 +25,6 @@ ENV GOFLAGS=-mod=vendor
|
||||
ENV CGO_ENABLED=0
|
||||
WORKDIR /src
|
||||
|
||||
FROM registry:$REGISTRY_VERSION AS registry
|
||||
|
||||
FROM moby/buildkit:$BUILDKIT_VERSION AS buildkit
|
||||
|
||||
FROM gobase AS gotestsum
|
||||
ARG GOTESTSUM_VERSION
|
||||
ENV GOFLAGS=
|
||||
@@ -77,11 +77,24 @@ FROM binaries-$TARGETOS AS binaries
|
||||
ARG BUILDKIT_SBOM_SCAN_STAGE=true
|
||||
|
||||
FROM gobase AS integration-test-base
|
||||
RUN apk add --no-cache docker runc containerd
|
||||
# https://github.com/docker/docker/blob/master/project/PACKAGERS.md#runtime-dependencies
|
||||
RUN apk add --no-cache \
|
||||
btrfs-progs \
|
||||
e2fsprogs \
|
||||
e2fsprogs-extra \
|
||||
ip6tables \
|
||||
iptables \
|
||||
openssl \
|
||||
shadow-uidmap \
|
||||
xfsprogs \
|
||||
xz
|
||||
COPY --link --from=gotestsum /out/gotestsum /usr/bin/
|
||||
COPY --link --from=registry /bin/registry /usr/bin/
|
||||
COPY --link --from=docker-engine / /usr/bin/
|
||||
COPY --link --from=docker-cli / /usr/bin/
|
||||
COPY --link --from=buildkit /usr/bin/buildkitd /usr/bin/
|
||||
COPY --link --from=buildkit /usr/bin/buildctl /usr/bin/
|
||||
COPY --link --from=undock /usr/local/bin/undock /usr/bin/
|
||||
COPY --link --from=binaries /buildx /usr/bin/
|
||||
|
||||
FROM integration-test-base AS integration-test
|
||||
@@ -102,7 +115,7 @@ FROM scratch AS release
|
||||
COPY --from=releaser /out/ /
|
||||
|
||||
# Shell
|
||||
FROM docker:$DOCKERD_VERSION AS dockerd-release
|
||||
FROM docker:$DOCKER_VERSION AS dockerd-release
|
||||
FROM alpine AS shell
|
||||
RUN apk add --no-cache iptables tmux git vim less openssh
|
||||
RUN mkdir -p /usr/local/lib/docker/cli-plugins && ln -s /usr/local/bin/buildx /usr/local/lib/docker/cli-plugins/docker-buildx
|
||||
|
||||
@@ -41,12 +41,10 @@ Key features:
|
||||
- [`buildx imagetools create`](docs/reference/buildx_imagetools_create.md)
|
||||
- [`buildx imagetools inspect`](docs/reference/buildx_imagetools_inspect.md)
|
||||
- [`buildx inspect`](docs/reference/buildx_inspect.md)
|
||||
- [`buildx install`](docs/reference/buildx_install.md)
|
||||
- [`buildx ls`](docs/reference/buildx_ls.md)
|
||||
- [`buildx prune`](docs/reference/buildx_prune.md)
|
||||
- [`buildx rm`](docs/reference/buildx_rm.md)
|
||||
- [`buildx stop`](docs/reference/buildx_stop.md)
|
||||
- [`buildx uninstall`](docs/reference/buildx_uninstall.md)
|
||||
- [`buildx use`](docs/reference/buildx_use.md)
|
||||
- [`buildx version`](docs/reference/buildx_version.md)
|
||||
- [Contributing](#contributing)
|
||||
@@ -71,8 +69,9 @@ for Windows and macOS.
|
||||
|
||||
## Linux packages
|
||||
|
||||
Docker Linux packages also include Docker Buildx when installed using the
|
||||
[DEB or RPM packages](https://docs.docker.com/engine/install/).
|
||||
Docker Engine package repositories contain Docker Buildx packages when installed according to the
|
||||
[Docker Engine install documentation](https://docs.docker.com/engine/install/). Install the
|
||||
`docker-buildx-plugin` package to install the Buildx plugin.
|
||||
|
||||
## Manual download
|
||||
|
||||
|
||||
302
bake/bake.go
302
bake/bake.go
@@ -11,16 +11,19 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
composecli "github.com/compose-spec/compose-go/cli"
|
||||
composecli "github.com/compose-spec/compose-go/v2/cli"
|
||||
"github.com/docker/buildx/bake/hclparser"
|
||||
"github.com/docker/buildx/build"
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/config"
|
||||
dockeropts "github.com/docker/cli/opts"
|
||||
hcl "github.com/hashicorp/hcl/v2"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/session/auth/authprovider"
|
||||
"github.com/pkg/errors"
|
||||
@@ -55,7 +58,7 @@ func defaultFilenames() []string {
|
||||
return names
|
||||
}
|
||||
|
||||
func ReadLocalFiles(names []string) ([]File, error) {
|
||||
func ReadLocalFiles(names []string, stdin io.Reader, l progress.SubLogger) ([]File, error) {
|
||||
isDefault := false
|
||||
if len(names) == 0 {
|
||||
isDefault = true
|
||||
@@ -63,20 +66,26 @@ func ReadLocalFiles(names []string) ([]File, error) {
|
||||
}
|
||||
out := make([]File, 0, len(names))
|
||||
|
||||
setStatus := func(st *client.VertexStatus) {
|
||||
if l != nil {
|
||||
l.SetStatus(st)
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range names {
|
||||
var dt []byte
|
||||
var err error
|
||||
if n == "-" {
|
||||
dt, err = io.ReadAll(os.Stdin)
|
||||
dt, err = readWithProgress(stdin, setStatus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
dt, err = os.ReadFile(n)
|
||||
dt, err = readFileWithProgress(n, isDefault, setStatus)
|
||||
if dt == nil && err == nil {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
if isDefault && errors.Is(err, os.ErrNotExist) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -85,6 +94,88 @@ func ReadLocalFiles(names []string) ([]File, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func readFileWithProgress(fname string, isDefault bool, setStatus func(st *client.VertexStatus)) (dt []byte, err error) {
|
||||
st := &client.VertexStatus{
|
||||
ID: "reading " + fname,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
now := time.Now()
|
||||
st.Completed = &now
|
||||
if dt != nil || err != nil {
|
||||
setStatus(st)
|
||||
}
|
||||
}()
|
||||
|
||||
now := time.Now()
|
||||
st.Started = &now
|
||||
|
||||
f, err := os.Open(fname)
|
||||
if err != nil {
|
||||
if isDefault && errors.Is(err, os.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
setStatus(st)
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st.Total = info.Size()
|
||||
setStatus(st)
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := f.Read(buf)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dt = append(dt, buf[:n]...)
|
||||
st.Current += int64(n)
|
||||
setStatus(st)
|
||||
}
|
||||
|
||||
return dt, nil
|
||||
}
|
||||
|
||||
func readWithProgress(r io.Reader, setStatus func(st *client.VertexStatus)) (dt []byte, err error) {
|
||||
st := &client.VertexStatus{
|
||||
ID: "reading from stdin",
|
||||
}
|
||||
|
||||
defer func() {
|
||||
now := time.Now()
|
||||
st.Completed = &now
|
||||
setStatus(st)
|
||||
}()
|
||||
|
||||
now := time.Now()
|
||||
st.Started = &now
|
||||
setStatus(st)
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := r.Read(buf)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dt = append(dt, buf[:n]...)
|
||||
st.Current += int64(n)
|
||||
setStatus(st)
|
||||
}
|
||||
|
||||
return dt, nil
|
||||
}
|
||||
|
||||
func ListTargets(files []File) ([]string, error) {
|
||||
c, err := ParseFiles(files, nil)
|
||||
if err != nil {
|
||||
@@ -248,7 +339,7 @@ func ParseFiles(files []File, defaults map[string]string) (_ *Config, err error)
|
||||
}
|
||||
|
||||
if len(hclFiles) > 0 {
|
||||
renamed, err := hclparser.Parse(hcl.MergeFiles(hclFiles), hclparser.Opt{
|
||||
renamed, err := hclparser.Parse(hclparser.MergeFiles(hclFiles), hclparser.Opt{
|
||||
LookupVar: os.LookupEnv,
|
||||
Vars: defaults,
|
||||
ValidateLabel: validateTargetName,
|
||||
@@ -587,9 +678,10 @@ type Target struct {
|
||||
Name string `json:"-" hcl:"name,label" cty:"name"`
|
||||
|
||||
// Inherits is the only field that cannot be overridden with --set
|
||||
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
|
||||
Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional" cty:"inherits"`
|
||||
|
||||
Annotations []string `json:"annotations,omitempty" hcl:"annotations,optional" cty:"annotations"`
|
||||
Attest []string `json:"attest,omitempty" hcl:"attest,optional" cty:"attest"`
|
||||
Context *string `json:"context,omitempty" hcl:"context,optional" cty:"context"`
|
||||
Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional" cty:"contexts"`
|
||||
Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional" cty:"dockerfile"`
|
||||
@@ -608,6 +700,8 @@ type Target struct {
|
||||
NoCache *bool `json:"no-cache,omitempty" hcl:"no-cache,optional" cty:"no-cache"`
|
||||
NetworkMode *string `json:"-" hcl:"-" cty:"-"`
|
||||
NoCacheFilter []string `json:"no-cache-filter,omitempty" hcl:"no-cache-filter,optional" cty:"no-cache-filter"`
|
||||
ShmSize *string `json:"shm-size,omitempty" hcl:"shm-size,optional"`
|
||||
Ulimits []string `json:"ulimits,omitempty" hcl:"ulimits,optional"`
|
||||
// IMPORTANT: if you add more fields here, do not forget to update newOverrides and docs/bake-reference.md.
|
||||
|
||||
// linked is a private field to mark a target used as a linked one
|
||||
@@ -620,6 +714,7 @@ var _ hclparser.WithEvalContexts = &Group{}
|
||||
var _ hclparser.WithGetName = &Group{}
|
||||
|
||||
func (t *Target) normalize() {
|
||||
t.Annotations = removeDupes(t.Annotations)
|
||||
t.Attest = removeAttestDupes(t.Attest)
|
||||
t.Tags = removeDupes(t.Tags)
|
||||
t.Secrets = removeDupes(t.Secrets)
|
||||
@@ -629,6 +724,7 @@ func (t *Target) normalize() {
|
||||
t.CacheTo = removeDupes(t.CacheTo)
|
||||
t.Outputs = removeDupes(t.Outputs)
|
||||
t.NoCacheFilter = removeDupes(t.NoCacheFilter)
|
||||
t.Ulimits = removeDupes(t.Ulimits)
|
||||
|
||||
for k, v := range t.Contexts {
|
||||
if v == "" {
|
||||
@@ -680,6 +776,9 @@ func (t *Target) Merge(t2 *Target) {
|
||||
if t2.Target != nil {
|
||||
t.Target = t2.Target
|
||||
}
|
||||
if t2.Annotations != nil { // merge
|
||||
t.Annotations = append(t.Annotations, t2.Annotations...)
|
||||
}
|
||||
if t2.Attest != nil { // merge
|
||||
t.Attest = append(t.Attest, t2.Attest...)
|
||||
t.Attest = removeAttestDupes(t.Attest)
|
||||
@@ -714,6 +813,12 @@ func (t *Target) Merge(t2 *Target) {
|
||||
if t2.NoCacheFilter != nil { // merge
|
||||
t.NoCacheFilter = append(t.NoCacheFilter, t2.NoCacheFilter...)
|
||||
}
|
||||
if t2.ShmSize != nil { // no merge
|
||||
t.ShmSize = t2.ShmSize
|
||||
}
|
||||
if t2.Ulimits != nil { // merge
|
||||
t.Ulimits = append(t.Ulimits, t2.Ulimits...)
|
||||
}
|
||||
t.Inherits = append(t.Inherits, t2.Inherits...)
|
||||
}
|
||||
|
||||
@@ -766,6 +871,8 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
|
||||
t.Platforms = o.ArrValue
|
||||
case "output":
|
||||
t.Outputs = o.ArrValue
|
||||
case "annotations":
|
||||
t.Annotations = append(t.Annotations, o.ArrValue...)
|
||||
case "attest":
|
||||
t.Attest = append(t.Attest, o.ArrValue...)
|
||||
case "no-cache":
|
||||
@@ -776,6 +883,10 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
|
||||
t.NoCache = &noCache
|
||||
case "no-cache-filter":
|
||||
t.NoCacheFilter = o.ArrValue
|
||||
case "shm-size":
|
||||
t.ShmSize = &value
|
||||
case "ulimits":
|
||||
t.Ulimits = o.ArrValue
|
||||
case "pull":
|
||||
pull, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
@@ -783,19 +894,17 @@ func (t *Target) AddOverrides(overrides map[string]Override) error {
|
||||
}
|
||||
t.Pull = &pull
|
||||
case "push":
|
||||
_, err := strconv.ParseBool(value)
|
||||
push, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return errors.Errorf("invalid value %s for boolean key push", value)
|
||||
}
|
||||
if len(t.Outputs) == 0 {
|
||||
t.Outputs = append(t.Outputs, "type=image,push=true")
|
||||
} else {
|
||||
for i, output := range t.Outputs {
|
||||
if typ := parseOutputType(output); typ == "image" || typ == "registry" {
|
||||
t.Outputs[i] = t.Outputs[i] + ",push=" + value
|
||||
}
|
||||
}
|
||||
t.Outputs = setPushOverride(t.Outputs, push)
|
||||
case "load":
|
||||
load, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return errors.Errorf("invalid value %s for boolean key load", value)
|
||||
}
|
||||
t.Outputs = setLoadOverride(t.Outputs, load)
|
||||
default:
|
||||
return errors.Errorf("unknown key: %s", keys[0])
|
||||
}
|
||||
@@ -852,8 +961,10 @@ func (t *Target) GetEvalContexts(ectx *hcl.EvalContext, block *hcl.Block, loadDe
|
||||
for _, e := range ectxs {
|
||||
e2 := ectx.NewChild()
|
||||
e2.Variables = make(map[string]cty.Value)
|
||||
for k, v := range e.Variables {
|
||||
e2.Variables[k] = v
|
||||
if e != ectx {
|
||||
for k, v := range e.Variables {
|
||||
e2.Variables[k] = v
|
||||
}
|
||||
}
|
||||
e2.Variables[k] = v
|
||||
ectxs2 = append(ectxs2, e2)
|
||||
@@ -912,12 +1023,17 @@ func (t *Target) GetName(ectx *hcl.EvalContext, block *hcl.Block, loadDeps func(
|
||||
}
|
||||
|
||||
func TargetsToBuildOpt(m map[string]*Target, inp *Input) (map[string]build.Options, error) {
|
||||
// make sure local credentials are loaded multiple times for different targets
|
||||
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
||||
authProvider := authprovider.NewDockerAuthProvider(dockerConfig, nil)
|
||||
|
||||
m2 := make(map[string]build.Options, len(m))
|
||||
for k, v := range m {
|
||||
bo, err := toBuildOpt(v, inp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bo.Session = append(bo.Session, authProvider)
|
||||
m2[k] = *bo
|
||||
}
|
||||
return m2, nil
|
||||
@@ -1038,6 +1154,9 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
if t.Dockerfile != nil {
|
||||
dockerfilePath = *t.Dockerfile
|
||||
}
|
||||
if !strings.HasPrefix(dockerfilePath, "cwd://") {
|
||||
dockerfilePath = path.Clean(dockerfilePath)
|
||||
}
|
||||
|
||||
bi := build.Inputs{
|
||||
ContextPath: contextPath,
|
||||
@@ -1048,12 +1167,44 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
bi.DockerfileInline = *t.DockerfileInline
|
||||
}
|
||||
updateContext(&bi, inp)
|
||||
if !build.IsRemoteURL(bi.ContextPath) && bi.ContextState == nil && !path.IsAbs(bi.DockerfilePath) {
|
||||
bi.DockerfilePath = path.Join(bi.ContextPath, bi.DockerfilePath)
|
||||
if strings.HasPrefix(bi.DockerfilePath, "cwd://") {
|
||||
// If Dockerfile is local for a remote invocation, we first check if
|
||||
// it's not outside the working directory and then resolve it to an
|
||||
// absolute path.
|
||||
bi.DockerfilePath = path.Clean(strings.TrimPrefix(bi.DockerfilePath, "cwd://"))
|
||||
if err := checkPath(bi.DockerfilePath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var err error
|
||||
bi.DockerfilePath, err = filepath.Abs(bi.DockerfilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if !build.IsRemoteURL(bi.DockerfilePath) && strings.HasPrefix(bi.ContextPath, "cwd://") && (inp != nil && build.IsRemoteURL(inp.URL)) {
|
||||
// We don't currently support reading a remote Dockerfile with a local
|
||||
// context when doing a remote invocation because we automatically
|
||||
// derive the dockerfile from the context atm:
|
||||
//
|
||||
// target "default" {
|
||||
// context = BAKE_CMD_CONTEXT
|
||||
// dockerfile = "Dockerfile.app"
|
||||
// }
|
||||
//
|
||||
// > docker buildx bake https://github.com/foo/bar.git
|
||||
// failed to solve: failed to read dockerfile: open /var/lib/docker/tmp/buildkit-mount3004544897/Dockerfile.app: no such file or directory
|
||||
//
|
||||
// To avoid mistakenly reading a local Dockerfile, we check if the
|
||||
// Dockerfile exists locally and if so, we error out.
|
||||
if _, err := os.Stat(filepath.Join(path.Clean(strings.TrimPrefix(bi.ContextPath, "cwd://")), bi.DockerfilePath)); err == nil {
|
||||
return nil, errors.Errorf("reading a dockerfile for a remote build invocation is currently not supported")
|
||||
}
|
||||
}
|
||||
if strings.HasPrefix(bi.ContextPath, "cwd://") {
|
||||
bi.ContextPath = path.Clean(strings.TrimPrefix(bi.ContextPath, "cwd://"))
|
||||
}
|
||||
if !build.IsRemoteURL(bi.ContextPath) && bi.ContextState == nil && !path.IsAbs(bi.DockerfilePath) {
|
||||
bi.DockerfilePath = path.Join(bi.ContextPath, bi.DockerfilePath)
|
||||
}
|
||||
for k, v := range bi.NamedContexts {
|
||||
if strings.HasPrefix(v.Path, "cwd://") {
|
||||
bi.NamedContexts[k] = build.NamedContext{Path: path.Clean(strings.TrimPrefix(v.Path, "cwd://"))}
|
||||
@@ -1094,6 +1245,12 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
if t.NetworkMode != nil {
|
||||
networkMode = *t.NetworkMode
|
||||
}
|
||||
shmSize := new(dockeropts.MemBytes)
|
||||
if t.ShmSize != nil {
|
||||
if err := shmSize.Set(*t.ShmSize); err != nil {
|
||||
return nil, errors.Errorf("invalid value %s for membytes key shm-size", *t.ShmSize)
|
||||
}
|
||||
}
|
||||
|
||||
bo := &build.Options{
|
||||
Inputs: bi,
|
||||
@@ -1105,6 +1262,7 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
Pull: pull,
|
||||
NetworkMode: networkMode,
|
||||
Linked: t.linked,
|
||||
ShmSize: *shmSize,
|
||||
}
|
||||
|
||||
platforms, err := platformutil.Parse(t.Platforms)
|
||||
@@ -1113,9 +1271,6 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
}
|
||||
bo.Platforms = platforms
|
||||
|
||||
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
||||
bo.Session = append(bo.Session, authprovider.NewDockerAuthProvider(dockerConfig))
|
||||
|
||||
secrets, err := buildflags.ParseSecretSpecs(t.Secrets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1164,6 +1319,16 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
annotations, err := buildflags.ParseAnnotations(t.Annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, e := range bo.Exports {
|
||||
for k, v := range annotations {
|
||||
e.Attrs[k.String()] = v
|
||||
}
|
||||
}
|
||||
|
||||
attests, err := buildflags.ParseAttests(t.Attest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1175,6 +1340,14 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ulimits := dockeropts.NewUlimitOpt(nil)
|
||||
for _, field := range t.Ulimits {
|
||||
if err := ulimits.Set(field); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
bo.Ulimits = ulimits
|
||||
|
||||
return bo, nil
|
||||
}
|
||||
|
||||
@@ -1219,23 +1392,90 @@ func removeAttestDupes(s []string) []string {
|
||||
return res
|
||||
}
|
||||
|
||||
func parseOutputType(str string) string {
|
||||
func parseOutput(str string) map[string]string {
|
||||
csvReader := csv.NewReader(strings.NewReader(str))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
res := map[string]string{}
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
if parts[0] == "type" {
|
||||
return parts[1]
|
||||
}
|
||||
res[parts[0]] = parts[1]
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func parseOutputType(str string) string {
|
||||
if out := parseOutput(str); out != nil {
|
||||
if v, ok := out["type"]; ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func setPushOverride(outputs []string, push bool) []string {
|
||||
var out []string
|
||||
setPush := true
|
||||
for _, output := range outputs {
|
||||
typ := parseOutputType(output)
|
||||
if typ == "image" || typ == "registry" {
|
||||
// no need to set push if image or registry types already defined
|
||||
setPush = false
|
||||
if typ == "registry" {
|
||||
if !push {
|
||||
// don't set registry output if "push" is false
|
||||
continue
|
||||
}
|
||||
// no need to set "push" attribute to true for registry
|
||||
out = append(out, output)
|
||||
continue
|
||||
}
|
||||
out = append(out, output+",push="+strconv.FormatBool(push))
|
||||
} else {
|
||||
if typ != "docker" {
|
||||
// if there is any output that is not docker, don't set "push"
|
||||
setPush = false
|
||||
}
|
||||
out = append(out, output)
|
||||
}
|
||||
}
|
||||
if push && setPush {
|
||||
out = append(out, "type=image,push=true")
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func setLoadOverride(outputs []string, load bool) []string {
|
||||
if !load {
|
||||
return outputs
|
||||
}
|
||||
setLoad := true
|
||||
for _, output := range outputs {
|
||||
if typ := parseOutputType(output); typ == "docker" {
|
||||
if v := parseOutput(output); v != nil {
|
||||
// dest set means we want to output as tar so don't set load
|
||||
if _, ok := v["dest"]; !ok {
|
||||
setLoad = false
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if typ != "image" && typ != "registry" && typ != "oci" {
|
||||
// if there is any output that is not an image, registry
|
||||
// or oci, don't set "load" similar to push override
|
||||
setLoad = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if setLoad {
|
||||
outputs = append(outputs, "type=docker")
|
||||
}
|
||||
return outputs
|
||||
}
|
||||
|
||||
func validateTargetName(name string) error {
|
||||
if !targetNamePattern.MatchString(name) {
|
||||
return errors.Errorf("only %q are allowed", validTargetNameChars)
|
||||
|
||||
@@ -3,10 +3,12 @@ package bake
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -20,6 +22,8 @@ target "webDEP" {
|
||||
VAR_BOTH = "webDEP"
|
||||
}
|
||||
no-cache = true
|
||||
shm-size = "128m"
|
||||
ulimits = ["nofile=1024:1024"]
|
||||
}
|
||||
|
||||
target "webapp" {
|
||||
@@ -43,6 +47,8 @@ target "webapp" {
|
||||
require.Equal(t, ".", *m["webapp"].Context)
|
||||
require.Equal(t, ptrstr("webDEP"), m["webapp"].Args["VAR_INHERITED"])
|
||||
require.Equal(t, true, *m["webapp"].NoCache)
|
||||
require.Equal(t, "128m", *m["webapp"].ShmSize)
|
||||
require.Equal(t, []string{"nofile=1024:1024"}, m["webapp"].Ulimits)
|
||||
require.Nil(t, m["webapp"].Pull)
|
||||
|
||||
require.Equal(t, 1, len(g))
|
||||
@@ -127,6 +133,12 @@ target "webapp" {
|
||||
require.Equal(t, []string{"webapp"}, g["default"].Targets)
|
||||
})
|
||||
|
||||
t.Run("ShmSizeOverride", func(t *testing.T) {
|
||||
m, _, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.shm-size=256m"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "256m", *m["webapp"].ShmSize)
|
||||
})
|
||||
|
||||
t.Run("PullOverride", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
m, g, err := ReadTargets(ctx, []File{fp}, []string{"webapp"}, []string{"webapp.pull=false"}, nil)
|
||||
@@ -205,48 +217,252 @@ target "webapp" {
|
||||
}
|
||||
|
||||
func TestPushOverride(t *testing.T) {
|
||||
t.Parallel()
|
||||
t.Run("empty output", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(m["app"].Outputs))
|
||||
require.Equal(t, "type=image,push=true", m["app"].Outputs[0])
|
||||
})
|
||||
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
t.Run("type image", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=image,compression=zstd"]
|
||||
}`),
|
||||
}
|
||||
ctx := context.TODO()
|
||||
m, _, err := ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"*.push=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(m["app"].Outputs))
|
||||
require.Equal(t, "type=image,compression=zstd,push=true", m["app"].Outputs[0])
|
||||
})
|
||||
|
||||
require.Equal(t, 1, len(m["app"].Outputs))
|
||||
require.Equal(t, "type=image,compression=zstd,push=true", m["app"].Outputs[0])
|
||||
|
||||
fp = File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
t.Run("type image push false", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=image,compression=zstd"]
|
||||
}`),
|
||||
}
|
||||
ctx = context.TODO()
|
||||
m, _, err = ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"*.push=false"}, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=false"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(m["app"].Outputs))
|
||||
require.Equal(t, "type=image,compression=zstd,push=false", m["app"].Outputs[0])
|
||||
})
|
||||
|
||||
require.Equal(t, 1, len(m["app"].Outputs))
|
||||
require.Equal(t, "type=image,compression=zstd,push=false", m["app"].Outputs[0])
|
||||
|
||||
fp = File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
t.Run("type registry", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=registry"]
|
||||
}`),
|
||||
}
|
||||
ctx = context.TODO()
|
||||
m, _, err = ReadTargets(ctx, []File{fp}, []string{"app"}, []string{"*.push=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(m["app"].Outputs))
|
||||
require.Equal(t, "type=registry", m["app"].Outputs[0])
|
||||
})
|
||||
|
||||
require.Equal(t, 1, len(m["app"].Outputs))
|
||||
require.Equal(t, "type=image,push=true", m["app"].Outputs[0])
|
||||
t.Run("type registry push false", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=registry"]
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.push=false"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 0, len(m["app"].Outputs))
|
||||
})
|
||||
|
||||
t.Run("type local and empty target", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "foo" {
|
||||
output = [ "type=local,dest=out" ]
|
||||
}
|
||||
target "bar" {
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.push=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(m))
|
||||
require.Equal(t, 1, len(m["foo"].Outputs))
|
||||
require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs)
|
||||
require.Equal(t, 1, len(m["bar"].Outputs))
|
||||
require.Equal(t, []string{"type=image,push=true"}, m["bar"].Outputs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadOverride(t *testing.T) {
|
||||
t.Run("empty output", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(m["app"].Outputs))
|
||||
require.Equal(t, "type=docker", m["app"].Outputs[0])
|
||||
})
|
||||
|
||||
t.Run("type docker", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=docker"]
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(m["app"].Outputs))
|
||||
require.Equal(t, []string{"type=docker"}, m["app"].Outputs)
|
||||
})
|
||||
|
||||
t.Run("type image", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=image"]
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(m["app"].Outputs))
|
||||
require.Equal(t, []string{"type=image", "type=docker"}, m["app"].Outputs)
|
||||
})
|
||||
|
||||
t.Run("type image load false", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=image"]
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=false"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(m["app"].Outputs))
|
||||
require.Equal(t, []string{"type=image"}, m["app"].Outputs)
|
||||
})
|
||||
|
||||
t.Run("type registry", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=registry"]
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(m["app"].Outputs))
|
||||
require.Equal(t, []string{"type=registry", "type=docker"}, m["app"].Outputs)
|
||||
})
|
||||
|
||||
t.Run("type oci", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=oci,dest=out"]
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(m["app"].Outputs))
|
||||
require.Equal(t, []string{"type=oci,dest=out", "type=docker"}, m["app"].Outputs)
|
||||
})
|
||||
|
||||
t.Run("type docker with dest", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=docker,dest=out"]
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"app"}, []string{"*.load=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(m["app"].Outputs))
|
||||
require.Equal(t, []string{"type=docker,dest=out", "type=docker"}, m["app"].Outputs)
|
||||
})
|
||||
|
||||
t.Run("type local and empty target", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "foo" {
|
||||
output = [ "type=local,dest=out" ]
|
||||
}
|
||||
target "bar" {
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.load=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(m))
|
||||
require.Equal(t, 1, len(m["foo"].Outputs))
|
||||
require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs)
|
||||
require.Equal(t, 1, len(m["bar"].Outputs))
|
||||
require.Equal(t, []string{"type=docker"}, m["bar"].Outputs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadAndPushOverride(t *testing.T) {
|
||||
t.Run("type local and empty target", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "foo" {
|
||||
output = [ "type=local,dest=out" ]
|
||||
}
|
||||
target "bar" {
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo", "bar"}, []string{"*.load=true", "*.push=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(m))
|
||||
|
||||
require.Equal(t, 1, len(m["foo"].Outputs))
|
||||
sort.Strings(m["foo"].Outputs)
|
||||
require.Equal(t, []string{"type=local,dest=out"}, m["foo"].Outputs)
|
||||
|
||||
require.Equal(t, 2, len(m["bar"].Outputs))
|
||||
sort.Strings(m["bar"].Outputs)
|
||||
require.Equal(t, []string{"type=docker", "type=image,push=true"}, m["bar"].Outputs)
|
||||
})
|
||||
|
||||
t.Run("type registry", func(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "foo" {
|
||||
output = [ "type=registry" ]
|
||||
}`),
|
||||
}
|
||||
m, _, err := ReadTargets(context.TODO(), []File{fp}, []string{"foo"}, []string{"*.load=true", "*.push=true"}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(m))
|
||||
|
||||
require.Equal(t, 2, len(m["foo"].Outputs))
|
||||
sort.Strings(m["foo"].Outputs)
|
||||
require.Equal(t, []string{"type=docker", "type=registry"}, m["foo"].Outputs)
|
||||
})
|
||||
}
|
||||
|
||||
func TestReadTargetsCompose(t *testing.T) {
|
||||
@@ -373,7 +589,7 @@ services:
|
||||
require.Equal(t, []string{"web_app"}, g["default"].Targets)
|
||||
}
|
||||
|
||||
func TestHCLCwdPrefix(t *testing.T) {
|
||||
func TestHCLContextCwdPrefix(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
@@ -386,18 +602,49 @@ func TestHCLCwdPrefix(t *testing.T) {
|
||||
m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(m))
|
||||
_, ok := m["app"]
|
||||
require.True(t, ok)
|
||||
|
||||
_, err = TargetsToBuildOpt(m, &Input{})
|
||||
bo, err := TargetsToBuildOpt(m, &Input{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "test", *m["app"].Dockerfile)
|
||||
require.Equal(t, "foo", *m["app"].Context)
|
||||
|
||||
require.Equal(t, 1, len(g))
|
||||
require.Equal(t, []string{"app"}, g["default"].Targets)
|
||||
|
||||
require.Equal(t, 1, len(m))
|
||||
require.Contains(t, m, "app")
|
||||
assert.Equal(t, "test", *m["app"].Dockerfile)
|
||||
assert.Equal(t, "foo", *m["app"].Context)
|
||||
assert.Equal(t, "foo/test", bo["app"].Inputs.DockerfilePath)
|
||||
assert.Equal(t, "foo", bo["app"].Inputs.ContextPath)
|
||||
}
|
||||
|
||||
func TestHCLDockerfileCwdPrefix(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
context = "."
|
||||
dockerfile = "cwd://Dockerfile.app"
|
||||
}`),
|
||||
}
|
||||
ctx := context.TODO()
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
bo, err := TargetsToBuildOpt(m, &Input{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(g))
|
||||
require.Equal(t, []string{"app"}, g["default"].Targets)
|
||||
|
||||
require.Equal(t, 1, len(m))
|
||||
require.Contains(t, m, "app")
|
||||
assert.Equal(t, "cwd://Dockerfile.app", *m["app"].Dockerfile)
|
||||
assert.Equal(t, ".", *m["app"].Context)
|
||||
assert.Equal(t, filepath.Join(cwd, "Dockerfile.app"), bo["app"].Inputs.DockerfilePath)
|
||||
assert.Equal(t, ".", bo["app"].Inputs.ContextPath)
|
||||
}
|
||||
|
||||
func TestOverrideMerge(t *testing.T) {
|
||||
@@ -1398,7 +1645,7 @@ func TestReadLocalFilesDefault(t *testing.T) {
|
||||
for _, tf := range tt.filenames {
|
||||
require.NoError(t, os.WriteFile(tf, []byte(tf), 0644))
|
||||
}
|
||||
files, err := ReadLocalFiles(nil)
|
||||
files, err := ReadLocalFiles(nil, nil, nil)
|
||||
require.NoError(t, err)
|
||||
if len(files) == 0 {
|
||||
require.Equal(t, len(tt.expected), len(files))
|
||||
@@ -1450,3 +1697,31 @@ func TestAttestDuplicates(t *testing.T) {
|
||||
"provenance": ptrstr("type=provenance,mode=max"),
|
||||
}, opts["default"].Attests)
|
||||
}
|
||||
|
||||
func TestAnnotations(t *testing.T) {
|
||||
fp := File{
|
||||
Name: "docker-bake.hcl",
|
||||
Data: []byte(
|
||||
`target "app" {
|
||||
output = ["type=image,name=foo"]
|
||||
annotations = ["manifest[linux/amd64]:foo=bar"]
|
||||
}`),
|
||||
}
|
||||
ctx := context.TODO()
|
||||
m, g, err := ReadTargets(ctx, []File{fp}, []string{"app"}, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
bo, err := TargetsToBuildOpt(m, &Input{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(g))
|
||||
require.Equal(t, []string{"app"}, g["default"].Targets)
|
||||
|
||||
require.Equal(t, 1, len(m))
|
||||
require.Contains(t, m, "app")
|
||||
require.Equal(t, "type=image,name=foo", m["app"].Outputs[0])
|
||||
require.Equal(t, "manifest[linux/amd64]:foo=bar", m["app"].Annotations[0])
|
||||
|
||||
require.Len(t, bo["app"].Exports, 1)
|
||||
require.Equal(t, "bar", bo["app"].Exports[0].Attrs["annotation-manifest[linux/amd64].foo"])
|
||||
}
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
package bake
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/compose-spec/compose-go/dotenv"
|
||||
"github.com/compose-spec/compose-go/loader"
|
||||
compose "github.com/compose-spec/compose-go/types"
|
||||
"github.com/compose-spec/compose-go/v2/dotenv"
|
||||
"github.com/compose-spec/compose-go/v2/loader"
|
||||
composetypes "github.com/compose-spec/compose-go/v2/types"
|
||||
dockeropts "github.com/docker/cli/opts"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -17,9 +21,9 @@ func ParseComposeFiles(fs []File) (*Config, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var cfgs []compose.ConfigFile
|
||||
var cfgs []composetypes.ConfigFile
|
||||
for _, f := range fs {
|
||||
cfgs = append(cfgs, compose.ConfigFile{
|
||||
cfgs = append(cfgs, composetypes.ConfigFile{
|
||||
Filename: f.Name,
|
||||
Content: f.Data,
|
||||
})
|
||||
@@ -27,16 +31,17 @@ func ParseComposeFiles(fs []File) (*Config, error) {
|
||||
return ParseCompose(cfgs, envs)
|
||||
}
|
||||
|
||||
func ParseCompose(cfgs []compose.ConfigFile, envs map[string]string) (*Config, error) {
|
||||
func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Config, error) {
|
||||
if envs == nil {
|
||||
envs = make(map[string]string)
|
||||
}
|
||||
cfg, err := loader.Load(compose.ConfigDetails{
|
||||
cfg, err := loader.LoadWithContext(context.Background(), composetypes.ConfigDetails{
|
||||
ConfigFiles: cfgs,
|
||||
Environment: envs,
|
||||
}, func(options *loader.Options) {
|
||||
options.SetProjectName("bake", false)
|
||||
options.SkipNormalization = true
|
||||
options.Profiles = []string{"*"}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -50,6 +55,7 @@ func ParseCompose(cfgs []compose.ConfigFile, envs map[string]string) (*Config, e
|
||||
g := &Group{Name: "default"}
|
||||
|
||||
for _, s := range cfg.Services {
|
||||
s := s
|
||||
if s.Build == nil {
|
||||
continue
|
||||
}
|
||||
@@ -83,6 +89,24 @@ func ParseCompose(cfgs []compose.ConfigFile, envs map[string]string) (*Config, e
|
||||
}
|
||||
}
|
||||
|
||||
var shmSize *string
|
||||
if s.Build.ShmSize > 0 {
|
||||
shmSizeBytes := dockeropts.MemBytes(s.Build.ShmSize)
|
||||
shmSizeStr := shmSizeBytes.String()
|
||||
shmSize = &shmSizeStr
|
||||
}
|
||||
|
||||
var ulimits []string
|
||||
if s.Build.Ulimits != nil {
|
||||
for n, u := range s.Build.Ulimits {
|
||||
ulimit, err := units.ParseUlimit(fmt.Sprintf("%s=%d:%d", n, u.Soft, u.Hard))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ulimits = append(ulimits, ulimit.String())
|
||||
}
|
||||
}
|
||||
|
||||
var secrets []string
|
||||
for _, bs := range s.Build.Secrets {
|
||||
secret, err := composeToBuildkitSecret(bs, cfg.Secrets[bs.Source])
|
||||
@@ -119,6 +143,8 @@ func ParseCompose(cfgs []compose.ConfigFile, envs map[string]string) (*Config, e
|
||||
CacheTo: s.Build.CacheTo,
|
||||
NetworkMode: &s.Build.Network,
|
||||
Secrets: secrets,
|
||||
ShmSize: shmSize,
|
||||
Ulimits: ulimits,
|
||||
}
|
||||
if err = t.composeExtTarget(s.Build.Extensions); err != nil {
|
||||
return nil, err
|
||||
@@ -156,8 +182,8 @@ func validateComposeFile(dt []byte, fn string) (bool, error) {
|
||||
}
|
||||
|
||||
func validateCompose(dt []byte, envs map[string]string) error {
|
||||
_, err := loader.Load(compose.ConfigDetails{
|
||||
ConfigFiles: []compose.ConfigFile{
|
||||
_, err := loader.Load(composetypes.ConfigDetails{
|
||||
ConfigFiles: []composetypes.ConfigFile{
|
||||
{
|
||||
Content: dt,
|
||||
},
|
||||
@@ -220,7 +246,7 @@ func loadDotEnv(curenv map[string]string, workingDir string) (map[string]string,
|
||||
return curenv, nil
|
||||
}
|
||||
|
||||
func flatten(in compose.MappingWithEquals) map[string]*string {
|
||||
func flatten(in composetypes.MappingWithEquals) map[string]*string {
|
||||
if len(in) == 0 {
|
||||
return nil
|
||||
}
|
||||
@@ -324,8 +350,8 @@ func (t *Target) composeExtTarget(exts map[string]interface{}) error {
|
||||
|
||||
// composeToBuildkitSecret converts secret from compose format to buildkit's
|
||||
// csv format.
|
||||
func composeToBuildkitSecret(inp compose.ServiceSecretConfig, psecret compose.SecretConfig) (string, error) {
|
||||
if psecret.External.External {
|
||||
func composeToBuildkitSecret(inp composetypes.ServiceSecretConfig, psecret composetypes.SecretConfig) (string, error) {
|
||||
if psecret.External {
|
||||
return "", errors.Errorf("unsupported external secret %s", psecret.Name)
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
compose "github.com/compose-spec/compose-go/types"
|
||||
composetypes "github.com/compose-spec/compose-go/v2/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -22,7 +22,7 @@ services:
|
||||
build:
|
||||
context: ./dir
|
||||
additional_contexts:
|
||||
foo: /bar
|
||||
foo: ./bar
|
||||
dockerfile: Dockerfile-alternate
|
||||
network:
|
||||
none
|
||||
@@ -36,6 +36,8 @@ services:
|
||||
- token
|
||||
- aws
|
||||
webapp2:
|
||||
profiles:
|
||||
- test
|
||||
build:
|
||||
context: ./dir
|
||||
dockerfile_inline: |
|
||||
@@ -47,7 +49,7 @@ secrets:
|
||||
file: /root/.aws/credentials
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
@@ -60,12 +62,12 @@ secrets:
|
||||
return c.Targets[i].Name < c.Targets[j].Name
|
||||
})
|
||||
require.Equal(t, "db", c.Targets[0].Name)
|
||||
require.Equal(t, "./db", *c.Targets[0].Context)
|
||||
require.Equal(t, "db", *c.Targets[0].Context)
|
||||
require.Equal(t, []string{"docker.io/tonistiigi/db"}, c.Targets[0].Tags)
|
||||
|
||||
require.Equal(t, "webapp", c.Targets[1].Name)
|
||||
require.Equal(t, "./dir", *c.Targets[1].Context)
|
||||
require.Equal(t, map[string]string{"foo": "/bar"}, c.Targets[1].Contexts)
|
||||
require.Equal(t, "dir", *c.Targets[1].Context)
|
||||
require.Equal(t, map[string]string{"foo": "bar"}, c.Targets[1].Contexts)
|
||||
require.Equal(t, "Dockerfile-alternate", *c.Targets[1].Dockerfile)
|
||||
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||
require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"])
|
||||
@@ -78,7 +80,7 @@ secrets:
|
||||
}, c.Targets[1].Secrets)
|
||||
|
||||
require.Equal(t, "webapp2", c.Targets[2].Name)
|
||||
require.Equal(t, "./dir", *c.Targets[2].Context)
|
||||
require.Equal(t, "dir", *c.Targets[2].Context)
|
||||
require.Equal(t, "FROM alpine\n", *c.Targets[2].DockerfileInline)
|
||||
}
|
||||
|
||||
@@ -90,7 +92,7 @@ services:
|
||||
webapp:
|
||||
build: ./db
|
||||
`)
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(c.Groups))
|
||||
require.Equal(t, 1, len(c.Targets))
|
||||
@@ -109,7 +111,7 @@ services:
|
||||
target: webapp
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
@@ -134,7 +136,7 @@ services:
|
||||
target: webapp
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
sort.Slice(c.Targets, func(i, j int) bool {
|
||||
@@ -165,7 +167,7 @@ services:
|
||||
t.Setenv("BAR", "foo")
|
||||
t.Setenv("ZZZ_BAR", "zzz_foo")
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, sliceToMap(os.Environ()))
|
||||
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, sliceToMap(os.Environ()))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ptrstr("bar"), c.Targets[0].Args["FOO"])
|
||||
require.Equal(t, ptrstr("zzz_foo"), c.Targets[0].Args["BAR"])
|
||||
@@ -179,7 +181,7 @@ services:
|
||||
entrypoint: echo 1
|
||||
`)
|
||||
|
||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -204,7 +206,7 @@ networks:
|
||||
gateway: 10.5.0.254
|
||||
`)
|
||||
|
||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -221,7 +223,7 @@ services:
|
||||
- bar
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []string{"foo", "bar"}, c.Targets[0].Tags)
|
||||
}
|
||||
@@ -258,7 +260,7 @@ networks:
|
||||
name: test-net
|
||||
`)
|
||||
|
||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -301,6 +303,11 @@ services:
|
||||
args:
|
||||
CT_ECR: foo
|
||||
CT_TAG: bar
|
||||
shm_size: 128m
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 1024
|
||||
hard: 1024
|
||||
x-bake:
|
||||
secret:
|
||||
- id=mysecret,src=/local/secret
|
||||
@@ -311,7 +318,7 @@ services:
|
||||
no-cache: true
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
sort.Slice(c.Targets, func(i, j int) bool {
|
||||
@@ -330,6 +337,8 @@ services:
|
||||
require.Equal(t, []string{"linux/arm64"}, c.Targets[1].Platforms)
|
||||
require.Equal(t, []string{"type=docker"}, c.Targets[1].Outputs)
|
||||
require.Equal(t, newBool(true), c.Targets[1].NoCache)
|
||||
require.Equal(t, ptrstr("128MiB"), c.Targets[1].ShmSize)
|
||||
require.Equal(t, []string{"nofile=1024:1024"}, c.Targets[1].Ulimits)
|
||||
}
|
||||
|
||||
func TestComposeExtDedup(t *testing.T) {
|
||||
@@ -355,7 +364,7 @@ services:
|
||||
- type=local,dest=path/to/cache
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(c.Targets))
|
||||
require.Equal(t, []string{"ct-addon:foo", "ct-addon:baz"}, c.Targets[0].Tags)
|
||||
@@ -388,7 +397,7 @@ services:
|
||||
- ` + envf.Name() + `
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]*string{"CT_ECR": ptrstr("foo"), "FOO": ptrstr("bsdf -csdf"), "NODE_ENV": ptrstr("test")}, c.Targets[0].Args)
|
||||
}
|
||||
@@ -434,7 +443,7 @@ services:
|
||||
published: "3306"
|
||||
protocol: tcp
|
||||
`)
|
||||
_, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -480,7 +489,7 @@ func TestServiceName(t *testing.T) {
|
||||
for _, tt := range cases {
|
||||
tt := tt
|
||||
t.Run(tt.svc, func(t *testing.T) {
|
||||
_, err := ParseCompose([]compose.ConfigFile{{Content: []byte(`
|
||||
_, err := ParseCompose([]composetypes.ConfigFile{{Content: []byte(`
|
||||
services:
|
||||
` + tt.svc + `:
|
||||
build:
|
||||
@@ -551,7 +560,7 @@ services:
|
||||
for _, tt := range cases {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := ParseCompose([]compose.ConfigFile{{Content: tt.dt}}, nil)
|
||||
_, err := ParseCompose([]composetypes.ConfigFile{{Content: tt.dt}}, nil)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
@@ -649,11 +658,90 @@ services:
|
||||
bar: "baz"
|
||||
`)
|
||||
|
||||
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, nil)
|
||||
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, c.Targets[0].Args)
|
||||
}
|
||||
|
||||
func TestDependsOn(t *testing.T) {
|
||||
var dt = []byte(`
|
||||
services:
|
||||
foo:
|
||||
build:
|
||||
context: .
|
||||
ports:
|
||||
- 3306:3306
|
||||
depends_on:
|
||||
- bar
|
||||
bar:
|
||||
build:
|
||||
context: .
|
||||
`)
|
||||
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestInclude(t *testing.T) {
|
||||
tmpdir := t.TempDir()
|
||||
|
||||
err := os.WriteFile(filepath.Join(tmpdir, "compose-foo.yml"), []byte(`
|
||||
services:
|
||||
foo:
|
||||
build:
|
||||
context: .
|
||||
target: buildfoo
|
||||
ports:
|
||||
- 3306:3306
|
||||
`), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
var dt = []byte(`
|
||||
include:
|
||||
- compose-foo.yml
|
||||
|
||||
services:
|
||||
bar:
|
||||
build:
|
||||
context: .
|
||||
target: buildbar
|
||||
`)
|
||||
|
||||
chdir(t, tmpdir)
|
||||
c, err := ParseComposeFiles([]File{{
|
||||
Name: "composetypes.yml",
|
||||
Data: dt,
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
sort.Slice(c.Targets, func(i, j int) bool {
|
||||
return c.Targets[i].Name < c.Targets[j].Name
|
||||
})
|
||||
require.Equal(t, "bar", c.Targets[0].Name)
|
||||
require.Equal(t, "buildbar", *c.Targets[0].Target)
|
||||
require.Equal(t, "foo", c.Targets[1].Name)
|
||||
require.Equal(t, "buildfoo", *c.Targets[1].Target)
|
||||
}
|
||||
|
||||
func TestDevelop(t *testing.T) {
|
||||
var dt = []byte(`
|
||||
services:
|
||||
scratch:
|
||||
build:
|
||||
context: ./webapp
|
||||
develop:
|
||||
watch:
|
||||
- path: ./webapp/html
|
||||
action: sync
|
||||
target: /var/www
|
||||
ignore:
|
||||
- node_modules/
|
||||
`)
|
||||
|
||||
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// chdir changes the current working directory to the named directory,
|
||||
// and then restore the original working directory at the end of the test.
|
||||
func chdir(t *testing.T, dir string) {
|
||||
|
||||
@@ -634,6 +634,29 @@ func TestHCLMultiFileAttrs(t *testing.T) {
|
||||
require.Equal(t, ptrstr("pre-ghi"), c.Targets[0].Args["v1"])
|
||||
}
|
||||
|
||||
func TestHCLMultiFileGlobalAttrs(t *testing.T) {
|
||||
dt := []byte(`
|
||||
FOO = "abc"
|
||||
target "app" {
|
||||
args = {
|
||||
v1 = "pre-${FOO}"
|
||||
}
|
||||
}
|
||||
`)
|
||||
dt2 := []byte(`
|
||||
FOO = "def"
|
||||
`)
|
||||
|
||||
c, err := ParseFiles([]File{
|
||||
{Data: dt, Name: "c1.hcl"},
|
||||
{Data: dt2, Name: "c2.hcl"},
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(c.Targets))
|
||||
require.Equal(t, c.Targets[0].Name, "app")
|
||||
require.Equal(t, "pre-def", *c.Targets[0].Args["v1"])
|
||||
}
|
||||
|
||||
func TestHCLDuplicateTarget(t *testing.T) {
|
||||
dt := []byte(`
|
||||
target "app" {
|
||||
@@ -1090,6 +1113,27 @@ func TestHCLMatrixBadTypes(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestHCLMatrixWithGlobalTarget(t *testing.T) {
|
||||
dt := []byte(`
|
||||
target "x" {
|
||||
tags = ["a", "b"]
|
||||
}
|
||||
|
||||
target "default" {
|
||||
tags = target.x.tags
|
||||
matrix = {
|
||||
dummy = [""]
|
||||
}
|
||||
}
|
||||
`)
|
||||
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
require.Equal(t, "x", c.Targets[0].Name)
|
||||
require.Equal(t, "default", c.Targets[1].Name)
|
||||
require.Equal(t, []string{"a", "b"}, c.Targets[1].Tags)
|
||||
}
|
||||
|
||||
func TestJSONAttributes(t *testing.T) {
|
||||
dt := []byte(`{"FOO": "abc", "variable": {"BAR": {"default": "def"}}, "target": { "app": { "args": {"v1": "pre-${FOO}-${BAR}"}} } }`)
|
||||
|
||||
@@ -1401,6 +1445,39 @@ func TestVarUnsupportedType(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestHCLIndexOfFunc(t *testing.T) {
|
||||
dt := []byte(`
|
||||
variable "APP_VERSIONS" {
|
||||
default = [
|
||||
"1.42.4",
|
||||
"1.42.3"
|
||||
]
|
||||
}
|
||||
target "default" {
|
||||
args = {
|
||||
APP_VERSION = app_version
|
||||
}
|
||||
matrix = {
|
||||
app_version = APP_VERSIONS
|
||||
}
|
||||
name="app-${replace(app_version, ".", "-")}"
|
||||
tags = [
|
||||
"app:${app_version}",
|
||||
indexof(APP_VERSIONS, app_version) == 0 ? "app:latest" : "",
|
||||
]
|
||||
}
|
||||
`)
|
||||
|
||||
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 2, len(c.Targets))
|
||||
require.Equal(t, "app-1-42-4", c.Targets[0].Name)
|
||||
require.Equal(t, "app:latest", c.Targets[0].Tags[1])
|
||||
require.Equal(t, "app-1-42-3", c.Targets[1].Name)
|
||||
require.Empty(t, c.Targets[1].Tags[1])
|
||||
}
|
||||
|
||||
func ptrstr(s interface{}) *string {
|
||||
var n *string
|
||||
if reflect.ValueOf(s).Kind() == reflect.String {
|
||||
|
||||
@@ -613,7 +613,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) (map[string]map[string][]string
|
||||
|
||||
attrs, diags := b.JustAttributes()
|
||||
if diags.HasErrors() {
|
||||
if d := removeAttributesDiags(diags, reserved, p.vars); len(d) > 0 {
|
||||
if d := removeAttributesDiags(diags, reserved, p.vars, attrs); len(d) > 0 {
|
||||
return nil, d
|
||||
}
|
||||
}
|
||||
@@ -631,13 +631,14 @@ func Parse(b hcl.Body, opt Opt, val interface{}) (map[string]map[string][]string
|
||||
}
|
||||
|
||||
for _, a := range content.Attributes {
|
||||
a := a
|
||||
return nil, hcl.Diagnostics{
|
||||
&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Invalid attribute",
|
||||
Detail: "global attributes currently not supported",
|
||||
Subject: &a.Range,
|
||||
Context: &a.Range,
|
||||
Subject: a.Range.Ptr(),
|
||||
Context: a.Range.Ptr(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -660,13 +661,14 @@ func Parse(b hcl.Body, opt Opt, val interface{}) (map[string]map[string][]string
|
||||
var subject *hcl.Range
|
||||
var context *hcl.Range
|
||||
if p.funcs[k].Params != nil {
|
||||
subject = &p.funcs[k].Params.Range
|
||||
subject = p.funcs[k].Params.Range.Ptr()
|
||||
context = subject
|
||||
} else {
|
||||
for _, block := range blocks.Blocks {
|
||||
block := block
|
||||
if block.Type == "function" && len(block.Labels) == 1 && block.Labels[0] == k {
|
||||
subject = &block.LabelRanges[0]
|
||||
context = &block.DefRange
|
||||
subject = block.LabelRanges[0].Ptr()
|
||||
context = block.DefRange.Ptr()
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -732,6 +734,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) (map[string]map[string][]string
|
||||
|
||||
diags = hcl.Diagnostics{}
|
||||
for _, b := range content.Blocks {
|
||||
b := b
|
||||
v := reflect.ValueOf(val)
|
||||
|
||||
err := p.resolveBlock(b, nil)
|
||||
@@ -742,7 +745,7 @@ func Parse(b hcl.Body, opt Opt, val interface{}) (map[string]map[string][]string
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
return nil, wrapErrorDiagnostic("Invalid block", err, &b.LabelRanges[0], &b.DefRange)
|
||||
return nil, wrapErrorDiagnostic("Invalid block", err, b.LabelRanges[0].Ptr(), b.DefRange.Ptr())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -854,7 +857,7 @@ func getNameIndex(v reflect.Value) (int, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func removeAttributesDiags(diags hcl.Diagnostics, reserved map[string]struct{}, vars map[string]*variable) hcl.Diagnostics {
|
||||
func removeAttributesDiags(diags hcl.Diagnostics, reserved map[string]struct{}, vars map[string]*variable, attrs hcl.Attributes) hcl.Diagnostics {
|
||||
var fdiags hcl.Diagnostics
|
||||
for _, d := range diags {
|
||||
if fout := func(d *hcl.Diagnostic) bool {
|
||||
@@ -876,6 +879,12 @@ func removeAttributesDiags(diags hcl.Diagnostics, reserved map[string]struct{},
|
||||
return true
|
||||
}
|
||||
}
|
||||
for a := range attrs {
|
||||
// Do the same for attributes
|
||||
if strings.HasPrefix(d.Detail, fmt.Sprintf(`Argument "%s" was already set at `, a)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}(d); !fout {
|
||||
fdiags = append(fdiags, d)
|
||||
|
||||
230
bake/hclparser/merged.go
Normal file
230
bake/hclparser/merged.go
Normal file
@@ -0,0 +1,230 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
// Forked from https://github.com/hashicorp/hcl/blob/4679383728fe331fc8a6b46036a27b8f818d9bc0/merged.go
|
||||
|
||||
package hclparser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
)
|
||||
|
||||
// MergeFiles combines the given files to produce a single body that contains
|
||||
// configuration from all of the given files.
|
||||
//
|
||||
// The ordering of the given files decides the order in which contained
|
||||
// elements will be returned. If any top-level attributes are defined with
|
||||
// the same name across multiple files, a diagnostic will be produced from
|
||||
// the Content and PartialContent methods describing this error in a
|
||||
// user-friendly way.
|
||||
func MergeFiles(files []*hcl.File) hcl.Body {
|
||||
var bodies []hcl.Body
|
||||
for _, file := range files {
|
||||
bodies = append(bodies, file.Body)
|
||||
}
|
||||
return MergeBodies(bodies)
|
||||
}
|
||||
|
||||
// MergeBodies is like MergeFiles except it deals directly with bodies, rather
|
||||
// than with entire files.
|
||||
func MergeBodies(bodies []hcl.Body) hcl.Body {
|
||||
if len(bodies) == 0 {
|
||||
// Swap out for our singleton empty body, to reduce the number of
|
||||
// empty slices we have hanging around.
|
||||
return emptyBody
|
||||
}
|
||||
|
||||
// If any of the given bodies are already merged bodies, we'll unpack
|
||||
// to flatten to a single mergedBodies, since that's conceptually simpler.
|
||||
// This also, as a side-effect, eliminates any empty bodies, since
|
||||
// empties are merged bodies with no inner bodies.
|
||||
var newLen int
|
||||
var flatten bool
|
||||
for _, body := range bodies {
|
||||
if children, merged := body.(mergedBodies); merged {
|
||||
newLen += len(children)
|
||||
flatten = true
|
||||
} else {
|
||||
newLen++
|
||||
}
|
||||
}
|
||||
|
||||
if !flatten { // not just newLen == len, because we might have mergedBodies with single bodies inside
|
||||
return mergedBodies(bodies)
|
||||
}
|
||||
|
||||
if newLen == 0 {
|
||||
// Don't allocate a new empty when we already have one
|
||||
return emptyBody
|
||||
}
|
||||
|
||||
n := make([]hcl.Body, 0, newLen)
|
||||
for _, body := range bodies {
|
||||
if children, merged := body.(mergedBodies); merged {
|
||||
n = append(n, children...)
|
||||
} else {
|
||||
n = append(n, body)
|
||||
}
|
||||
}
|
||||
return mergedBodies(n)
|
||||
}
|
||||
|
||||
var emptyBody = mergedBodies([]hcl.Body{})
|
||||
|
||||
// EmptyBody returns a body with no content. This body can be used as a
|
||||
// placeholder when a body is required but no body content is available.
|
||||
func EmptyBody() hcl.Body {
|
||||
return emptyBody
|
||||
}
|
||||
|
||||
type mergedBodies []hcl.Body
|
||||
|
||||
// Content returns the content produced by applying the given schema to all
|
||||
// of the merged bodies and merging the result.
|
||||
//
|
||||
// Although required attributes _are_ supported, they should be used sparingly
|
||||
// with merged bodies since in this case there is no contextual information
|
||||
// with which to return good diagnostics. Applications working with merged
|
||||
// bodies may wish to mark all attributes as optional and then check for
|
||||
// required attributes afterwards, to produce better diagnostics.
|
||||
func (mb mergedBodies) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
|
||||
// the returned body will always be empty in this case, because mergedContent
|
||||
// will only ever call Content on the child bodies.
|
||||
content, _, diags := mb.mergedContent(schema, false)
|
||||
return content, diags
|
||||
}
|
||||
|
||||
func (mb mergedBodies) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
|
||||
return mb.mergedContent(schema, true)
|
||||
}
|
||||
|
||||
func (mb mergedBodies) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
|
||||
attrs := make(map[string]*hcl.Attribute)
|
||||
var diags hcl.Diagnostics
|
||||
|
||||
for _, body := range mb {
|
||||
thisAttrs, thisDiags := body.JustAttributes()
|
||||
|
||||
if len(thisDiags) != 0 {
|
||||
diags = append(diags, thisDiags...)
|
||||
}
|
||||
|
||||
if thisAttrs != nil {
|
||||
for name, attr := range thisAttrs {
|
||||
if existing := attrs[name]; existing != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Duplicate argument",
|
||||
Detail: fmt.Sprintf(
|
||||
"Argument %q was already set at %s",
|
||||
name, existing.NameRange.String(),
|
||||
),
|
||||
Subject: thisAttrs[name].NameRange.Ptr(),
|
||||
})
|
||||
}
|
||||
attrs[name] = attr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attrs, diags
|
||||
}
|
||||
|
||||
func (mb mergedBodies) MissingItemRange() hcl.Range {
|
||||
if len(mb) == 0 {
|
||||
// Nothing useful to return here, so we'll return some garbage.
|
||||
return hcl.Range{
|
||||
Filename: "<empty>",
|
||||
}
|
||||
}
|
||||
|
||||
// arbitrarily use the first body's missing item range
|
||||
return mb[0].MissingItemRange()
|
||||
}
|
||||
|
||||
func (mb mergedBodies) mergedContent(schema *hcl.BodySchema, partial bool) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
|
||||
// We need to produce a new schema with none of the attributes marked as
|
||||
// required, since _any one_ of our bodies can contribute an attribute value.
|
||||
// We'll separately check that all required attributes are present at
|
||||
// the end.
|
||||
mergedSchema := &hcl.BodySchema{
|
||||
Blocks: schema.Blocks,
|
||||
}
|
||||
for _, attrS := range schema.Attributes {
|
||||
mergedAttrS := attrS
|
||||
mergedAttrS.Required = false
|
||||
mergedSchema.Attributes = append(mergedSchema.Attributes, mergedAttrS)
|
||||
}
|
||||
|
||||
var mergedLeftovers []hcl.Body
|
||||
content := &hcl.BodyContent{
|
||||
Attributes: map[string]*hcl.Attribute{},
|
||||
}
|
||||
|
||||
var diags hcl.Diagnostics
|
||||
for _, body := range mb {
|
||||
var thisContent *hcl.BodyContent
|
||||
var thisLeftovers hcl.Body
|
||||
var thisDiags hcl.Diagnostics
|
||||
|
||||
if partial {
|
||||
thisContent, thisLeftovers, thisDiags = body.PartialContent(mergedSchema)
|
||||
} else {
|
||||
thisContent, thisDiags = body.Content(mergedSchema)
|
||||
}
|
||||
|
||||
if thisLeftovers != nil {
|
||||
mergedLeftovers = append(mergedLeftovers, thisLeftovers)
|
||||
}
|
||||
if len(thisDiags) != 0 {
|
||||
diags = append(diags, thisDiags...)
|
||||
}
|
||||
|
||||
if thisContent.Attributes != nil {
|
||||
for name, attr := range thisContent.Attributes {
|
||||
if existing := content.Attributes[name]; existing != nil {
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Duplicate argument",
|
||||
Detail: fmt.Sprintf(
|
||||
"Argument %q was already set at %s",
|
||||
name, existing.NameRange.String(),
|
||||
),
|
||||
Subject: thisContent.Attributes[name].NameRange.Ptr(),
|
||||
})
|
||||
}
|
||||
content.Attributes[name] = attr
|
||||
}
|
||||
}
|
||||
|
||||
if len(thisContent.Blocks) != 0 {
|
||||
content.Blocks = append(content.Blocks, thisContent.Blocks...)
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, we check for required attributes.
|
||||
for _, attrS := range schema.Attributes {
|
||||
if !attrS.Required {
|
||||
continue
|
||||
}
|
||||
|
||||
if content.Attributes[attrS.Name] == nil {
|
||||
// We don't have any context here to produce a good diagnostic,
|
||||
// which is why we warn in the Content docstring to minimize the
|
||||
// use of required attributes on merged bodies.
|
||||
diags = diags.Append(&hcl.Diagnostic{
|
||||
Severity: hcl.DiagError,
|
||||
Summary: "Missing required argument",
|
||||
Detail: fmt.Sprintf(
|
||||
"The argument %q is required, but was not set.",
|
||||
attrS.Name,
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
leftoverBody := MergeBodies(mergedLeftovers)
|
||||
return content, leftoverBody, diags
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/hashicorp/go-cty-funcs/uuid"
|
||||
"github.com/hashicorp/hcl/v2/ext/tryfunc"
|
||||
"github.com/hashicorp/hcl/v2/ext/typeexpr"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
"github.com/zclconf/go-cty/cty/function"
|
||||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
||||
@@ -52,6 +53,7 @@ var stdlibFunctions = map[string]function.Function{
|
||||
"hasindex": stdlib.HasIndexFunc,
|
||||
"indent": stdlib.IndentFunc,
|
||||
"index": stdlib.IndexFunc,
|
||||
"indexof": indexOfFunc,
|
||||
"int": stdlib.IntFunc,
|
||||
"join": stdlib.JoinFunc,
|
||||
"jsondecode": stdlib.JSONDecodeFunc,
|
||||
@@ -115,6 +117,51 @@ var stdlibFunctions = map[string]function.Function{
|
||||
"zipmap": stdlib.ZipmapFunc,
|
||||
}
|
||||
|
||||
// indexOfFunc constructs a function that finds the element index for a given
|
||||
// value in a list.
|
||||
var indexOfFunc = function.New(&function.Spec{
|
||||
Params: []function.Parameter{
|
||||
{
|
||||
Name: "list",
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
{
|
||||
Name: "value",
|
||||
Type: cty.DynamicPseudoType,
|
||||
},
|
||||
},
|
||||
Type: function.StaticReturnType(cty.Number),
|
||||
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
|
||||
if !(args[0].Type().IsListType() || args[0].Type().IsTupleType()) {
|
||||
return cty.NilVal, errors.New("argument must be a list or tuple")
|
||||
}
|
||||
|
||||
if !args[0].IsKnown() {
|
||||
return cty.UnknownVal(cty.Number), nil
|
||||
}
|
||||
|
||||
if args[0].LengthInt() == 0 { // Easy path
|
||||
return cty.NilVal, errors.New("cannot search an empty list")
|
||||
}
|
||||
|
||||
for it := args[0].ElementIterator(); it.Next(); {
|
||||
i, v := it.Element()
|
||||
eq, err := stdlib.Equal(v, args[1])
|
||||
if err != nil {
|
||||
return cty.NilVal, err
|
||||
}
|
||||
if !eq.IsKnown() {
|
||||
return cty.UnknownVal(cty.Number), nil
|
||||
}
|
||||
if eq.True() {
|
||||
return i, nil
|
||||
}
|
||||
}
|
||||
return cty.NilVal, errors.New("item not found")
|
||||
|
||||
},
|
||||
})
|
||||
|
||||
// timestampFunc constructs a function that returns a string representation of the current date and time.
|
||||
//
|
||||
// This function was imported from terraform's datetime utilities.
|
||||
|
||||
49
bake/hclparser/stdlib_test.go
Normal file
49
bake/hclparser/stdlib_test.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package hclparser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
func TestIndexOf(t *testing.T) {
|
||||
type testCase struct {
|
||||
input cty.Value
|
||||
key cty.Value
|
||||
want cty.Value
|
||||
wantErr bool
|
||||
}
|
||||
tests := map[string]testCase{
|
||||
"index 0": {
|
||||
input: cty.TupleVal([]cty.Value{cty.StringVal("one"), cty.NumberIntVal(2.0), cty.NumberIntVal(3), cty.StringVal("four")}),
|
||||
key: cty.StringVal("one"),
|
||||
want: cty.NumberIntVal(0),
|
||||
},
|
||||
"index 3": {
|
||||
input: cty.TupleVal([]cty.Value{cty.StringVal("one"), cty.NumberIntVal(2.0), cty.NumberIntVal(3), cty.StringVal("four")}),
|
||||
key: cty.StringVal("four"),
|
||||
want: cty.NumberIntVal(3),
|
||||
},
|
||||
"index -1": {
|
||||
input: cty.TupleVal([]cty.Value{cty.StringVal("one"), cty.NumberIntVal(2.0), cty.NumberIntVal(3), cty.StringVal("four")}),
|
||||
key: cty.StringVal("3"),
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range tests {
|
||||
name, test := name, test
|
||||
t.Run(name, func(t *testing.T) {
|
||||
got, err := indexOfFunc.Call([]cty.Value{test.input, test.key})
|
||||
if err != nil {
|
||||
if test.wantErr {
|
||||
return
|
||||
}
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if !got.RawEquals(test.want) {
|
||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
@@ -23,13 +25,34 @@ type Input struct {
|
||||
}
|
||||
|
||||
func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
|
||||
var session []session.Attachable
|
||||
var sessions []session.Attachable
|
||||
var filename string
|
||||
|
||||
st, ok := dockerui.DetectGitContext(url, false)
|
||||
if ok {
|
||||
ssh, err := controllerapi.CreateSSH([]*controllerapi.SSH{{ID: "default"}})
|
||||
if err == nil {
|
||||
session = append(session, ssh)
|
||||
if ssh, err := controllerapi.CreateSSH([]*controllerapi.SSH{{
|
||||
ID: "default",
|
||||
Paths: strings.Split(os.Getenv("BUILDX_BAKE_GIT_SSH"), ","),
|
||||
}}); err == nil {
|
||||
sessions = append(sessions, ssh)
|
||||
}
|
||||
var gitAuthSecrets []*controllerapi.Secret
|
||||
if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_TOKEN"); ok {
|
||||
gitAuthSecrets = append(gitAuthSecrets, &controllerapi.Secret{
|
||||
ID: llb.GitAuthTokenKey,
|
||||
Env: "BUILDX_BAKE_GIT_AUTH_TOKEN",
|
||||
})
|
||||
}
|
||||
if _, ok := os.LookupEnv("BUILDX_BAKE_GIT_AUTH_HEADER"); ok {
|
||||
gitAuthSecrets = append(gitAuthSecrets, &controllerapi.Secret{
|
||||
ID: llb.GitAuthHeaderKey,
|
||||
Env: "BUILDX_BAKE_GIT_AUTH_HEADER",
|
||||
})
|
||||
}
|
||||
if len(gitAuthSecrets) > 0 {
|
||||
if secrets, err := controllerapi.CreateSecrets(gitAuthSecrets); err == nil {
|
||||
sessions = append(sessions, secrets)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
st, filename, ok = dockerui.DetectHTTPContext(url)
|
||||
@@ -59,7 +82,7 @@ func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, name
|
||||
|
||||
ch, done := progress.NewChannel(pw)
|
||||
defer func() { <-done }()
|
||||
_, err = c.Build(ctx, client.SolveOpt{Session: session}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
|
||||
_, err = c.Build(ctx, client.SolveOpt{Session: sessions, Internal: true}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
|
||||
def, err := st.Marshal(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
1029
build/build.go
1029
build/build.go
File diff suppressed because it is too large
Load Diff
62
build/dial.go
Normal file
62
build/dial.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
"net"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func Dial(ctx context.Context, nodes []builder.Node, pw progress.Writer, platform *v1.Platform) (net.Conn, error) {
|
||||
nodes, err := filterAvailableNodes(nodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(nodes) == 0 {
|
||||
return nil, errors.New("no nodes available")
|
||||
}
|
||||
|
||||
var pls []v1.Platform
|
||||
if platform != nil {
|
||||
pls = []v1.Platform{*platform}
|
||||
}
|
||||
|
||||
opts := map[string]Options{"default": {Platforms: pls}}
|
||||
resolved, err := resolveDrivers(ctx, nodes, opts, pw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dialError error
|
||||
for _, ls := range resolved {
|
||||
for _, rn := range ls {
|
||||
if platform != nil {
|
||||
p := *platform
|
||||
var found bool
|
||||
for _, pp := range rn.platforms {
|
||||
if platforms.Only(p).Match(pp) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
conn, err := nodes[rn.driverIndex].Driver.Dial(ctx)
|
||||
if err == nil {
|
||||
return conn, nil
|
||||
}
|
||||
dialError = stderrors.Join(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.Wrap(dialError, "no nodes available")
|
||||
}
|
||||
312
build/driver.go
Normal file
312
build/driver.go
Normal file
@@ -0,0 +1,312 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/moby/buildkit/client"
|
||||
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/util/flightcontrol"
|
||||
"github.com/moby/buildkit/util/tracing"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type resolvedNode struct {
|
||||
resolver *nodeResolver
|
||||
driverIndex int
|
||||
platforms []specs.Platform
|
||||
}
|
||||
|
||||
func (dp resolvedNode) Node() builder.Node {
|
||||
return dp.resolver.nodes[dp.driverIndex]
|
||||
}
|
||||
|
||||
func (dp resolvedNode) Client(ctx context.Context) (*client.Client, error) {
|
||||
clients, err := dp.resolver.boot(ctx, []int{dp.driverIndex}, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clients[0], nil
|
||||
}
|
||||
|
||||
func (dp resolvedNode) BuildOpts(ctx context.Context) (gateway.BuildOpts, error) {
|
||||
opts, err := dp.resolver.opts(ctx, []int{dp.driverIndex}, nil)
|
||||
if err != nil {
|
||||
return gateway.BuildOpts{}, err
|
||||
}
|
||||
return opts[0], nil
|
||||
}
|
||||
|
||||
type matchMaker func(specs.Platform) platforms.MatchComparer
|
||||
|
||||
type nodeResolver struct {
|
||||
nodes []builder.Node
|
||||
clients flightcontrol.Group[*client.Client]
|
||||
opt flightcontrol.Group[gateway.BuildOpts]
|
||||
}
|
||||
|
||||
func resolveDrivers(ctx context.Context, nodes []builder.Node, opt map[string]Options, pw progress.Writer) (map[string][]*resolvedNode, error) {
|
||||
driverRes := newDriverResolver(nodes)
|
||||
drivers, err := driverRes.Resolve(ctx, opt, pw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return drivers, err
|
||||
}
|
||||
|
||||
func newDriverResolver(nodes []builder.Node) *nodeResolver {
|
||||
r := &nodeResolver{
|
||||
nodes: nodes,
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *nodeResolver) Resolve(ctx context.Context, opt map[string]Options, pw progress.Writer) (map[string][]*resolvedNode, error) {
|
||||
if len(r.nodes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
nodes := map[string][]*resolvedNode{}
|
||||
for k, opt := range opt {
|
||||
node, perfect, err := r.resolve(ctx, opt.Platforms, pw, platforms.OnlyStrict, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !perfect {
|
||||
break
|
||||
}
|
||||
nodes[k] = node
|
||||
}
|
||||
if len(nodes) != len(opt) {
|
||||
// if we didn't get a perfect match, we need to boot all drivers
|
||||
allIndexes := make([]int, len(r.nodes))
|
||||
for i := range allIndexes {
|
||||
allIndexes[i] = i
|
||||
}
|
||||
|
||||
clients, err := r.boot(ctx, allIndexes, pw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
eg, egCtx := errgroup.WithContext(ctx)
|
||||
workers := make([][]specs.Platform, len(clients))
|
||||
for i, c := range clients {
|
||||
i, c := i, c
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
eg.Go(func() error {
|
||||
ww, err := c.ListWorkers(egCtx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "listing workers")
|
||||
}
|
||||
|
||||
ps := make(map[string]specs.Platform, len(ww))
|
||||
for _, w := range ww {
|
||||
for _, p := range w.Platforms {
|
||||
pk := platforms.Format(platforms.Normalize(p))
|
||||
ps[pk] = p
|
||||
}
|
||||
}
|
||||
for _, p := range ps {
|
||||
workers[i] = append(workers[i], p)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// then we can attempt to match against all the available platforms
|
||||
// (this time we don't care about imperfect matches)
|
||||
nodes = map[string][]*resolvedNode{}
|
||||
for k, opt := range opt {
|
||||
node, _, err := r.resolve(ctx, opt.Platforms, pw, platforms.Only, func(idx int, n builder.Node) []specs.Platform {
|
||||
return workers[idx]
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
nodes[k] = node
|
||||
}
|
||||
}
|
||||
|
||||
idxs := make([]int, 0, len(r.nodes))
|
||||
for _, nodes := range nodes {
|
||||
for _, node := range nodes {
|
||||
idxs = append(idxs, node.driverIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// preload capabilities
|
||||
span, ctx := tracing.StartSpan(ctx, "load buildkit capabilities", trace.WithSpanKind(trace.SpanKindInternal))
|
||||
_, err := r.opts(ctx, idxs, pw)
|
||||
tracing.FinishWithError(span, err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (r *nodeResolver) resolve(ctx context.Context, ps []specs.Platform, pw progress.Writer, matcher matchMaker, additional func(idx int, n builder.Node) []specs.Platform) ([]*resolvedNode, bool, error) {
|
||||
if len(r.nodes) == 0 {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
perfect := true
|
||||
nodeIdxs := make([]int, 0)
|
||||
for _, p := range ps {
|
||||
idx := r.get(p, matcher, additional)
|
||||
if idx == -1 {
|
||||
idx = 0
|
||||
perfect = false
|
||||
}
|
||||
nodeIdxs = append(nodeIdxs, idx)
|
||||
}
|
||||
|
||||
var nodes []*resolvedNode
|
||||
if len(nodeIdxs) == 0 {
|
||||
nodes = append(nodes, &resolvedNode{
|
||||
resolver: r,
|
||||
driverIndex: 0,
|
||||
})
|
||||
} else {
|
||||
for i, idx := range nodeIdxs {
|
||||
node := &resolvedNode{
|
||||
resolver: r,
|
||||
driverIndex: idx,
|
||||
}
|
||||
if len(ps) > 0 {
|
||||
node.platforms = []specs.Platform{ps[i]}
|
||||
}
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
}
|
||||
|
||||
nodes = recombineNodes(nodes)
|
||||
if _, err := r.boot(ctx, nodeIdxs, pw); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return nodes, perfect, nil
|
||||
}
|
||||
|
||||
func (r *nodeResolver) get(p specs.Platform, matcher matchMaker, additionalPlatforms func(int, builder.Node) []specs.Platform) int {
|
||||
best := -1
|
||||
bestPlatform := specs.Platform{}
|
||||
for i, node := range r.nodes {
|
||||
platforms := node.Platforms
|
||||
if additionalPlatforms != nil {
|
||||
platforms = append([]specs.Platform{}, platforms...)
|
||||
platforms = append(platforms, additionalPlatforms(i, node)...)
|
||||
}
|
||||
for _, p2 := range platforms {
|
||||
m := matcher(p2)
|
||||
if !m.Match(p) {
|
||||
continue
|
||||
}
|
||||
|
||||
if best == -1 {
|
||||
best = i
|
||||
bestPlatform = p2
|
||||
continue
|
||||
}
|
||||
if matcher(p2).Less(p, bestPlatform) {
|
||||
best = i
|
||||
bestPlatform = p2
|
||||
}
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
func (r *nodeResolver) boot(ctx context.Context, idxs []int, pw progress.Writer) ([]*client.Client, error) {
|
||||
clients := make([]*client.Client, len(idxs))
|
||||
|
||||
baseCtx := ctx
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
for i, idx := range idxs {
|
||||
i, idx := i, idx
|
||||
eg.Go(func() error {
|
||||
c, err := r.clients.Do(ctx, fmt.Sprint(idx), func(ctx context.Context) (*client.Client, error) {
|
||||
if r.nodes[idx].Driver == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return driver.Boot(ctx, baseCtx, r.nodes[idx].Driver, pw)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clients[i] = c
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return clients, nil
|
||||
}
|
||||
|
||||
func (r *nodeResolver) opts(ctx context.Context, idxs []int, pw progress.Writer) ([]gateway.BuildOpts, error) {
|
||||
clients, err := r.boot(ctx, idxs, pw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bopts := make([]gateway.BuildOpts, len(clients))
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
for i, idxs := range idxs {
|
||||
i, idx := i, idxs
|
||||
c := clients[i]
|
||||
if c == nil {
|
||||
continue
|
||||
}
|
||||
eg.Go(func() error {
|
||||
opt, err := r.opt.Do(ctx, fmt.Sprint(idx), func(ctx context.Context) (gateway.BuildOpts, error) {
|
||||
opt := gateway.BuildOpts{}
|
||||
_, err := c.Build(ctx, client.SolveOpt{
|
||||
Internal: true,
|
||||
}, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
|
||||
opt = c.BuildOpts()
|
||||
return nil, nil
|
||||
}, nil)
|
||||
return opt, err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bopts[i] = opt
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bopts, nil
|
||||
}
|
||||
|
||||
// recombineDriverPairs recombines resolved nodes that are on the same driver
|
||||
// back together into a single node.
|
||||
func recombineNodes(nodes []*resolvedNode) []*resolvedNode {
|
||||
result := make([]*resolvedNode, 0, len(nodes))
|
||||
lookup := map[int]int{}
|
||||
for _, node := range nodes {
|
||||
if idx, ok := lookup[node.driverIndex]; ok {
|
||||
result[idx].platforms = append(result[idx].platforms, node.platforms...)
|
||||
} else {
|
||||
lookup[node.driverIndex] = len(result)
|
||||
result = append(result, node)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
315
build/driver_test.go
Normal file
315
build/driver_test.go
Normal file
@@ -0,0 +1,315 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/buildx/builder"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestFindDriverSanity(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.DefaultSpec()},
|
||||
})
|
||||
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.DefaultSpec()}, nil, platforms.OnlyStrict, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, 0, res[0].driverIndex)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
require.Equal(t, []specs.Platform{platforms.DefaultSpec()}, res[0].platforms)
|
||||
}
|
||||
|
||||
func TestFindDriverEmpty(t *testing.T) {
|
||||
r := makeTestResolver(nil)
|
||||
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.DefaultSpec()}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Nil(t, res)
|
||||
}
|
||||
|
||||
func TestFindDriverWeirdName(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/amd64")},
|
||||
"bbb": {platforms.MustParse("linux/foobar")},
|
||||
})
|
||||
|
||||
// find first platform
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/foobar")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, 1, res[0].driverIndex)
|
||||
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||
}
|
||||
|
||||
func TestFindDriverUnknown(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/amd64")},
|
||||
})
|
||||
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/riscv64")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.False(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, 0, res[0].driverIndex)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
}
|
||||
|
||||
func TestSelectNodeSinglePlatform(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/amd64")},
|
||||
"bbb": {platforms.MustParse("linux/riscv64")},
|
||||
})
|
||||
|
||||
// find first platform
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/amd64")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, 0, res[0].driverIndex)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
|
||||
// find second platform
|
||||
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/riscv64")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, 1, res[0].driverIndex)
|
||||
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||
|
||||
// find an unknown platform, should match the first driver
|
||||
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/s390x")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.False(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, 0, res[0].driverIndex)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
}
|
||||
|
||||
func TestSelectNodeMultiPlatform(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/amd64"), platforms.MustParse("linux/arm64")},
|
||||
"bbb": {platforms.MustParse("linux/riscv64")},
|
||||
})
|
||||
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/amd64")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, 0, res[0].driverIndex)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
|
||||
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm64")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, 0, res[0].driverIndex)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
|
||||
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/riscv64")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, 1, res[0].driverIndex)
|
||||
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||
}
|
||||
|
||||
func TestSelectNodeNonStrict(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/amd64")},
|
||||
"bbb": {platforms.MustParse("linux/arm64")},
|
||||
})
|
||||
|
||||
// arm64 should match itself
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm64")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||
|
||||
// arm64 may support arm/v8
|
||||
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v8")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||
|
||||
// arm64 may support arm/v7
|
||||
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v7")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||
}
|
||||
|
||||
func TestSelectNodeNonStrictARM(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/amd64")},
|
||||
"bbb": {platforms.MustParse("linux/arm64")},
|
||||
"ccc": {platforms.MustParse("linux/arm/v8")},
|
||||
})
|
||||
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v8")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "ccc", res[0].Node().Builder)
|
||||
|
||||
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v7")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "ccc", res[0].Node().Builder)
|
||||
}
|
||||
|
||||
func TestSelectNodeNonStrictLower(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/amd64")},
|
||||
"bbb": {platforms.MustParse("linux/arm/v7")},
|
||||
})
|
||||
|
||||
// v8 can't be built on v7 (so we should select the default)...
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v8")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.False(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
|
||||
// ...but v6 can be built on v8
|
||||
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v6")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||
}
|
||||
|
||||
func TestSelectNodePreferStart(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/amd64")},
|
||||
"bbb": {platforms.MustParse("linux/riscv64")},
|
||||
"ccc": {platforms.MustParse("linux/riscv64")},
|
||||
})
|
||||
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/riscv64")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||
}
|
||||
|
||||
func TestSelectNodePreferExact(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/arm/v8")},
|
||||
"bbb": {platforms.MustParse("linux/arm/v7")},
|
||||
})
|
||||
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v7")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||
}
|
||||
|
||||
func TestSelectNodeNoPlatform(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/foobar")},
|
||||
"bbb": {platforms.DefaultSpec()},
|
||||
})
|
||||
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
require.Empty(t, res[0].platforms)
|
||||
}
|
||||
|
||||
func TestSelectNodeAdditionalPlatforms(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/amd64")},
|
||||
"bbb": {platforms.MustParse("linux/arm/v8")},
|
||||
})
|
||||
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v7")}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "bbb", res[0].Node().Builder)
|
||||
|
||||
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{platforms.MustParse("linux/arm/v7")}, nil, platforms.Only, func(idx int, n builder.Node) []specs.Platform {
|
||||
if n.Builder == "aaa" {
|
||||
return []specs.Platform{platforms.MustParse("linux/arm/v7")}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
}
|
||||
|
||||
func TestSplitNodeMultiPlatform(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/amd64"), platforms.MustParse("linux/arm64")},
|
||||
"bbb": {platforms.MustParse("linux/riscv64")},
|
||||
})
|
||||
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{
|
||||
platforms.MustParse("linux/amd64"),
|
||||
platforms.MustParse("linux/arm64"),
|
||||
}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 1)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
|
||||
res, perfect, err = r.resolve(context.TODO(), []specs.Platform{
|
||||
platforms.MustParse("linux/amd64"),
|
||||
platforms.MustParse("linux/riscv64"),
|
||||
}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 2)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
require.Equal(t, "bbb", res[1].Node().Builder)
|
||||
}
|
||||
|
||||
func TestSplitNodeMultiPlatformNoUnify(t *testing.T) {
|
||||
r := makeTestResolver(map[string][]specs.Platform{
|
||||
"aaa": {platforms.MustParse("linux/amd64")},
|
||||
"bbb": {platforms.MustParse("linux/amd64"), platforms.MustParse("linux/riscv64")},
|
||||
})
|
||||
|
||||
// the "best" choice would be the node with both platforms, but we're using
|
||||
// a naive algorithm that doesn't try to unify the platforms
|
||||
res, perfect, err := r.resolve(context.TODO(), []specs.Platform{
|
||||
platforms.MustParse("linux/amd64"),
|
||||
platforms.MustParse("linux/riscv64"),
|
||||
}, nil, platforms.Only, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, perfect)
|
||||
require.Len(t, res, 2)
|
||||
require.Equal(t, "aaa", res[0].Node().Builder)
|
||||
require.Equal(t, "bbb", res[1].Node().Builder)
|
||||
}
|
||||
|
||||
func makeTestResolver(nodes map[string][]specs.Platform) *nodeResolver {
|
||||
var ns []builder.Node
|
||||
for name, platforms := range nodes {
|
||||
ns = append(ns, builder.Node{
|
||||
Builder: name,
|
||||
Platforms: platforms,
|
||||
})
|
||||
}
|
||||
sort.Slice(ns, func(i, j int) bool {
|
||||
return ns[i].Builder < ns[j].Builder
|
||||
})
|
||||
return newDriverResolver(ns)
|
||||
}
|
||||
72
build/git.go
72
build/git.go
@@ -9,16 +9,18 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/util/gitutil"
|
||||
"github.com/docker/buildx/util/osutil"
|
||||
"github.com/moby/buildkit/client"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const DockerfileLabel = "com.docker.image.source.entrypoint"
|
||||
|
||||
func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (res map[string]string, _ error) {
|
||||
res = make(map[string]string)
|
||||
func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (map[string]string, func(key, dir string, so *client.SolveOpt), error) {
|
||||
res := make(map[string]string)
|
||||
if contextPath == "" {
|
||||
return
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
setGitLabels := false
|
||||
@@ -37,7 +39,7 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
|
||||
}
|
||||
|
||||
if !setGitLabels && !setGitInfo {
|
||||
return
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// figure out in which directory the git command needs to run in
|
||||
@@ -45,27 +47,32 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
|
||||
if filepath.IsAbs(contextPath) {
|
||||
wd = contextPath
|
||||
} else {
|
||||
cwd, _ := os.Getwd()
|
||||
wd, _ = filepath.Abs(filepath.Join(cwd, contextPath))
|
||||
wd, _ = filepath.Abs(filepath.Join(osutil.GetWd(), contextPath))
|
||||
}
|
||||
wd = osutil.SanitizePath(wd)
|
||||
|
||||
gitc, err := gitutil.New(gitutil.WithContext(ctx), gitutil.WithWorkingDir(wd))
|
||||
if err != nil {
|
||||
if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() {
|
||||
return res, errors.New("buildx: git was not found in the system. Current commit information was not captured by the build")
|
||||
if st, err1 := os.Stat(path.Join(wd, ".git")); err1 == nil && st.IsDir() {
|
||||
return res, nil, errors.Wrap(err, "git was not found in the system")
|
||||
}
|
||||
return
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
if !gitc.IsInsideWorkTree() {
|
||||
if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() {
|
||||
return res, errors.New("buildx: failed to read current commit information with git rev-parse --is-inside-work-tree")
|
||||
return res, nil, errors.New("failed to read current commit information with git rev-parse --is-inside-work-tree")
|
||||
}
|
||||
return res, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
root, err := gitc.RootDir()
|
||||
if err != nil {
|
||||
return res, nil, errors.Wrap(err, "failed to get git root dir")
|
||||
}
|
||||
|
||||
if sha, err := gitc.FullCommit(); err != nil && !gitutil.IsUnknownRevision(err) {
|
||||
return res, errors.Wrapf(err, "buildx: failed to get git commit")
|
||||
return res, nil, errors.Wrap(err, "failed to get git commit")
|
||||
} else if sha != "" {
|
||||
checkDirty := false
|
||||
if v, ok := os.LookupEnv("BUILDX_GIT_CHECK_DIRTY"); ok {
|
||||
@@ -93,23 +100,32 @@ func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath st
|
||||
}
|
||||
}
|
||||
|
||||
if setGitLabels {
|
||||
if root, err := gitc.RootDir(); err != nil {
|
||||
return res, errors.Wrapf(err, "buildx: failed to get git root dir")
|
||||
} else if root != "" {
|
||||
if dockerfilePath == "" {
|
||||
dockerfilePath = filepath.Join(wd, "Dockerfile")
|
||||
}
|
||||
if !filepath.IsAbs(dockerfilePath) {
|
||||
cwd, _ := os.Getwd()
|
||||
dockerfilePath = filepath.Join(cwd, dockerfilePath)
|
||||
}
|
||||
dockerfilePath, _ = filepath.Rel(root, dockerfilePath)
|
||||
if !strings.HasPrefix(dockerfilePath, "..") {
|
||||
res["label:"+DockerfileLabel] = dockerfilePath
|
||||
}
|
||||
if setGitLabels && root != "" {
|
||||
if dockerfilePath == "" {
|
||||
dockerfilePath = filepath.Join(wd, "Dockerfile")
|
||||
}
|
||||
if !filepath.IsAbs(dockerfilePath) {
|
||||
dockerfilePath = filepath.Join(osutil.GetWd(), dockerfilePath)
|
||||
}
|
||||
if r, err := filepath.Rel(root, dockerfilePath); err == nil && !strings.HasPrefix(r, "..") {
|
||||
res["label:"+DockerfileLabel] = r
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return res, func(key, dir string, so *client.SolveOpt) {
|
||||
if !setGitInfo || root == "" {
|
||||
return
|
||||
}
|
||||
dir, err := filepath.Abs(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if lp, err := osutil.GetLongPathName(dir); err == nil {
|
||||
dir = lp
|
||||
}
|
||||
dir = osutil.SanitizePath(dir)
|
||||
if r, err := filepath.Rel(root, dir); err == nil && !strings.HasPrefix(r, "..") {
|
||||
so.FrontendAttrs["vcs:localdir:"+key] = r
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/buildx/util/gitutil"
|
||||
"github.com/moby/buildkit/client"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -30,7 +31,7 @@ func setupTest(tb testing.TB) {
|
||||
}
|
||||
|
||||
func TestGetGitAttributesNotGitRepo(t *testing.T) {
|
||||
_, err := getGitAttributes(context.Background(), t.TempDir(), "Dockerfile")
|
||||
_, _, err := getGitAttributes(context.Background(), t.TempDir(), "Dockerfile")
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -38,14 +39,14 @@ func TestGetGitAttributesBadGitRepo(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
require.NoError(t, os.MkdirAll(path.Join(tmp, ".git"), 0755))
|
||||
|
||||
_, err := getGitAttributes(context.Background(), tmp, "Dockerfile")
|
||||
_, _, err := getGitAttributes(context.Background(), tmp, "Dockerfile")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestGetGitAttributesNoContext(t *testing.T) {
|
||||
setupTest(t)
|
||||
|
||||
gitattrs, err := getGitAttributes(context.Background(), "", "Dockerfile")
|
||||
gitattrs, _, err := getGitAttributes(context.Background(), "", "Dockerfile")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, gitattrs)
|
||||
}
|
||||
@@ -114,7 +115,7 @@ func TestGetGitAttributes(t *testing.T) {
|
||||
if tt.envGitInfo != "" {
|
||||
t.Setenv("BUILDX_GIT_INFO", tt.envGitInfo)
|
||||
}
|
||||
gitattrs, err := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||
gitattrs, _, err := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||
require.NoError(t, err)
|
||||
for _, e := range tt.expected {
|
||||
assert.Contains(t, gitattrs, e)
|
||||
@@ -139,7 +140,7 @@ func TestGetGitAttributesDirty(t *testing.T) {
|
||||
require.NoError(t, os.WriteFile(filepath.Join("dir", "Dockerfile"), df, 0644))
|
||||
|
||||
t.Setenv("BUILDX_GIT_LABELS", "true")
|
||||
gitattrs, _ := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||
gitattrs, _, _ := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||
assert.Equal(t, 5, len(gitattrs))
|
||||
|
||||
assert.Contains(t, gitattrs, "label:"+DockerfileLabel)
|
||||
@@ -154,3 +155,55 @@ func TestGetGitAttributesDirty(t *testing.T) {
|
||||
assert.Contains(t, gitattrs, "vcs:revision")
|
||||
assert.True(t, strings.HasSuffix(gitattrs["vcs:revision"], "-dirty"))
|
||||
}
|
||||
|
||||
func TestLocalDirs(t *testing.T) {
|
||||
setupTest(t)
|
||||
|
||||
so := &client.SolveOpt{
|
||||
FrontendAttrs: map[string]string{},
|
||||
}
|
||||
|
||||
_, addVCSLocalDir, err := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, addVCSLocalDir)
|
||||
|
||||
require.NoError(t, setLocalMount("context", ".", so, addVCSLocalDir))
|
||||
require.Contains(t, so.FrontendAttrs, "vcs:localdir:context")
|
||||
assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:context"])
|
||||
|
||||
require.NoError(t, setLocalMount("dockerfile", ".", so, addVCSLocalDir))
|
||||
require.Contains(t, so.FrontendAttrs, "vcs:localdir:dockerfile")
|
||||
assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:dockerfile"])
|
||||
}
|
||||
|
||||
func TestLocalDirsSub(t *testing.T) {
|
||||
gitutil.Mktmp(t)
|
||||
|
||||
c, err := gitutil.New()
|
||||
require.NoError(t, err)
|
||||
gitutil.GitInit(c, t)
|
||||
|
||||
df := []byte("FROM alpine:latest\n")
|
||||
assert.NoError(t, os.MkdirAll("app", 0755))
|
||||
assert.NoError(t, os.WriteFile("app/Dockerfile", df, 0644))
|
||||
|
||||
gitutil.GitAdd(c, t, "app/Dockerfile")
|
||||
gitutil.GitCommit(c, t, "initial commit")
|
||||
gitutil.GitSetRemote(c, t, "origin", "git@github.com:docker/buildx.git")
|
||||
|
||||
so := &client.SolveOpt{
|
||||
FrontendAttrs: map[string]string{},
|
||||
}
|
||||
|
||||
_, addVCSLocalDir, err := getGitAttributes(context.Background(), ".", "app/Dockerfile")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, addVCSLocalDir)
|
||||
|
||||
require.NoError(t, setLocalMount("context", ".", so, addVCSLocalDir))
|
||||
require.Contains(t, so.FrontendAttrs, "vcs:localdir:context")
|
||||
assert.Equal(t, ".", so.FrontendAttrs["vcs:localdir:context"])
|
||||
|
||||
require.NoError(t, setLocalMount("dockerfile", "app", so, addVCSLocalDir))
|
||||
require.Contains(t, so.FrontendAttrs, "vcs:localdir:dockerfile")
|
||||
assert.Equal(t, "app", so.FrontendAttrs["vcs:localdir:dockerfile"])
|
||||
}
|
||||
|
||||
43
build/localstate.go
Normal file
43
build/localstate.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/localstate"
|
||||
"github.com/moby/buildkit/client"
|
||||
)
|
||||
|
||||
func saveLocalState(so *client.SolveOpt, target string, opts Options, node builder.Node, configDir string) error {
|
||||
var err error
|
||||
if so.Ref == "" {
|
||||
return nil
|
||||
}
|
||||
lp := opts.Inputs.ContextPath
|
||||
dp := opts.Inputs.DockerfilePath
|
||||
if lp != "" || dp != "" {
|
||||
if lp != "" {
|
||||
lp, err = filepath.Abs(lp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if dp != "" {
|
||||
dp, err = filepath.Abs(dp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
l, err := localstate.New(configDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return l.SaveRef(node.Builder, node.Name, so.Ref, localstate.State{
|
||||
Target: target,
|
||||
LocalPath: lp,
|
||||
DockerfilePath: dp,
|
||||
GroupRef: opts.GroupRef,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
641
build/opt.go
Normal file
641
build/opt.go
Normal file
@@ -0,0 +1,641 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/content/local"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/buildx/util/osutil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/client/ociindex"
|
||||
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/identity"
|
||||
"github.com/moby/buildkit/session/upload/uploadprovider"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
"github.com/moby/buildkit/util/apicaps"
|
||||
"github.com/moby/buildkit/util/entitlements"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/tonistiigi/fsutil"
|
||||
)
|
||||
|
||||
func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Options, bopts gateway.BuildOpts, configDir string, addVCSLocalDir func(key, dir string, so *client.SolveOpt), pw progress.Writer, docker *dockerutil.Client) (_ *client.SolveOpt, release func(), err error) {
|
||||
nodeDriver := node.Driver
|
||||
defers := make([]func(), 0, 2)
|
||||
releaseF := func() {
|
||||
for _, f := range defers {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
releaseF()
|
||||
}
|
||||
}()
|
||||
|
||||
// inline cache from build arg
|
||||
if v, ok := opt.BuildArgs["BUILDKIT_INLINE_CACHE"]; ok {
|
||||
if v, _ := strconv.ParseBool(v); v {
|
||||
opt.CacheTo = append(opt.CacheTo, client.CacheOptionsEntry{
|
||||
Type: "inline",
|
||||
Attrs: map[string]string{},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, e := range opt.CacheTo {
|
||||
if e.Type != "inline" && !nodeDriver.Features(ctx)[driver.CacheExport] {
|
||||
return nil, nil, notSupported(driver.CacheExport, nodeDriver, "https://docs.docker.com/go/build-cache-backends/")
|
||||
}
|
||||
}
|
||||
|
||||
cacheTo := make([]client.CacheOptionsEntry, 0, len(opt.CacheTo))
|
||||
for _, e := range opt.CacheTo {
|
||||
if e.Type == "gha" {
|
||||
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.gha")) {
|
||||
continue
|
||||
}
|
||||
} else if e.Type == "s3" {
|
||||
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.s3")) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
cacheTo = append(cacheTo, e)
|
||||
}
|
||||
|
||||
cacheFrom := make([]client.CacheOptionsEntry, 0, len(opt.CacheFrom))
|
||||
for _, e := range opt.CacheFrom {
|
||||
if e.Type == "gha" {
|
||||
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.gha")) {
|
||||
continue
|
||||
}
|
||||
} else if e.Type == "s3" {
|
||||
if !bopts.LLBCaps.Contains(apicaps.CapID("cache.s3")) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
cacheFrom = append(cacheFrom, e)
|
||||
}
|
||||
|
||||
so := client.SolveOpt{
|
||||
Ref: opt.Ref,
|
||||
Frontend: "dockerfile.v0",
|
||||
FrontendAttrs: map[string]string{},
|
||||
LocalMounts: map[string]fsutil.FS{},
|
||||
CacheExports: cacheTo,
|
||||
CacheImports: cacheFrom,
|
||||
AllowedEntitlements: opt.Allow,
|
||||
SourcePolicy: opt.SourcePolicy,
|
||||
}
|
||||
|
||||
if so.Ref == "" {
|
||||
so.Ref = identity.NewID()
|
||||
}
|
||||
|
||||
if opt.CgroupParent != "" {
|
||||
so.FrontendAttrs["cgroup-parent"] = opt.CgroupParent
|
||||
}
|
||||
|
||||
if v, ok := opt.BuildArgs["BUILDKIT_MULTI_PLATFORM"]; ok {
|
||||
if v, _ := strconv.ParseBool(v); v {
|
||||
so.FrontendAttrs["multi-platform"] = "true"
|
||||
}
|
||||
}
|
||||
|
||||
if multiDriver {
|
||||
// force creation of manifest list
|
||||
so.FrontendAttrs["multi-platform"] = "true"
|
||||
}
|
||||
|
||||
attests := make(map[string]string)
|
||||
for k, v := range opt.Attests {
|
||||
if v != nil {
|
||||
attests[k] = *v
|
||||
}
|
||||
}
|
||||
|
||||
supportAttestations := bopts.LLBCaps.Contains(apicaps.CapID("exporter.image.attestations")) && nodeDriver.Features(ctx)[driver.MultiPlatform]
|
||||
if len(attests) > 0 {
|
||||
if !supportAttestations {
|
||||
if !nodeDriver.Features(ctx)[driver.MultiPlatform] {
|
||||
return nil, nil, notSupported("Attestation", nodeDriver, "https://docs.docker.com/go/attestations/")
|
||||
}
|
||||
return nil, nil, errors.Errorf("Attestations are not supported by the current BuildKit daemon")
|
||||
}
|
||||
for k, v := range attests {
|
||||
so.FrontendAttrs["attest:"+k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := opt.Attests["provenance"]; !ok && supportAttestations {
|
||||
const noAttestEnv = "BUILDX_NO_DEFAULT_ATTESTATIONS"
|
||||
var noProv bool
|
||||
if v, ok := os.LookupEnv(noAttestEnv); ok {
|
||||
noProv, err = strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "invalid "+noAttestEnv)
|
||||
}
|
||||
}
|
||||
if !noProv {
|
||||
so.FrontendAttrs["attest:provenance"] = "mode=min,inline-only=true"
|
||||
}
|
||||
}
|
||||
|
||||
switch len(opt.Exports) {
|
||||
case 1:
|
||||
// valid
|
||||
case 0:
|
||||
if !noDefaultLoad() {
|
||||
if nodeDriver.IsMobyDriver() {
|
||||
// backwards compat for docker driver only:
|
||||
// this ensures the build results in a docker image.
|
||||
opt.Exports = []client.ExportEntry{{Type: "image", Attrs: map[string]string{}}}
|
||||
} else if nodeDriver.Features(ctx)[driver.DefaultLoad] {
|
||||
opt.Exports = []client.ExportEntry{{Type: "docker", Attrs: map[string]string{}}}
|
||||
}
|
||||
}
|
||||
default:
|
||||
if err := bopts.LLBCaps.Supports(pb.CapMultipleExporters); err != nil {
|
||||
return nil, nil, errors.Errorf("multiple outputs currently unsupported by the current BuildKit daemon, please upgrade to version v0.13+ or use a single output")
|
||||
}
|
||||
}
|
||||
|
||||
// fill in image exporter names from tags
|
||||
if len(opt.Tags) > 0 {
|
||||
tags := make([]string, len(opt.Tags))
|
||||
for i, tag := range opt.Tags {
|
||||
ref, err := reference.Parse(tag)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "invalid tag %q", tag)
|
||||
}
|
||||
tags[i] = ref.String()
|
||||
}
|
||||
for i, e := range opt.Exports {
|
||||
switch e.Type {
|
||||
case "image", "oci", "docker":
|
||||
opt.Exports[i].Attrs["name"] = strings.Join(tags, ",")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, e := range opt.Exports {
|
||||
if e.Type == "image" && e.Attrs["name"] == "" && e.Attrs["push"] != "" {
|
||||
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
|
||||
return nil, nil, errors.Errorf("tag is needed when pushing to registry")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cacheonly is a fake exporter to opt out of default behaviors
|
||||
exports := make([]client.ExportEntry, 0, len(opt.Exports))
|
||||
for _, e := range opt.Exports {
|
||||
if e.Type != "cacheonly" {
|
||||
exports = append(exports, e)
|
||||
}
|
||||
}
|
||||
opt.Exports = exports
|
||||
|
||||
// set up exporters
|
||||
for i, e := range opt.Exports {
|
||||
if e.Type == "oci" && !nodeDriver.Features(ctx)[driver.OCIExporter] {
|
||||
return nil, nil, notSupported(driver.OCIExporter, nodeDriver, "https://docs.docker.com/go/build-exporters/")
|
||||
}
|
||||
if e.Type == "docker" {
|
||||
features := docker.Features(ctx, e.Attrs["context"])
|
||||
if features[dockerutil.OCIImporter] && e.Output == nil {
|
||||
// rely on oci importer if available (which supports
|
||||
// multi-platform images), otherwise fall back to docker
|
||||
opt.Exports[i].Type = "oci"
|
||||
} else if len(opt.Platforms) > 1 || len(attests) > 0 {
|
||||
if e.Output != nil {
|
||||
return nil, nil, errors.Errorf("docker exporter does not support exporting manifest lists, use the oci exporter instead")
|
||||
}
|
||||
return nil, nil, errors.Errorf("docker exporter does not currently support exporting manifest lists")
|
||||
}
|
||||
if e.Output == nil {
|
||||
if nodeDriver.IsMobyDriver() {
|
||||
e.Type = "image"
|
||||
} else {
|
||||
w, cancel, err := docker.LoadImage(ctx, e.Attrs["context"], pw)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defers = append(defers, cancel)
|
||||
opt.Exports[i].Output = func(_ map[string]string) (io.WriteCloser, error) {
|
||||
return w, nil
|
||||
}
|
||||
}
|
||||
} else if !nodeDriver.Features(ctx)[driver.DockerExporter] {
|
||||
return nil, nil, notSupported(driver.DockerExporter, nodeDriver, "https://docs.docker.com/go/build-exporters/")
|
||||
}
|
||||
}
|
||||
if e.Type == "image" && nodeDriver.IsMobyDriver() {
|
||||
opt.Exports[i].Type = "moby"
|
||||
if e.Attrs["push"] != "" {
|
||||
if ok, _ := strconv.ParseBool(e.Attrs["push"]); ok {
|
||||
if ok, _ := strconv.ParseBool(e.Attrs["push-by-digest"]); ok {
|
||||
return nil, nil, errors.Errorf("push-by-digest is currently not implemented for docker driver, please create a new builder instance")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if e.Type == "docker" || e.Type == "image" || e.Type == "oci" {
|
||||
// inline buildinfo attrs from build arg
|
||||
if v, ok := opt.BuildArgs["BUILDKIT_INLINE_BUILDINFO_ATTRS"]; ok {
|
||||
e.Attrs["buildinfo-attrs"] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
so.Exports = opt.Exports
|
||||
so.Session = opt.Session
|
||||
|
||||
releaseLoad, err := loadInputs(ctx, nodeDriver, opt.Inputs, addVCSLocalDir, pw, &so)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defers = append(defers, releaseLoad)
|
||||
|
||||
if sharedKey := so.LocalDirs["context"]; sharedKey != "" {
|
||||
if p, err := filepath.Abs(sharedKey); err == nil {
|
||||
sharedKey = filepath.Base(p)
|
||||
}
|
||||
so.SharedKey = sharedKey + ":" + confutil.TryNodeIdentifier(configDir)
|
||||
}
|
||||
|
||||
if opt.Pull {
|
||||
so.FrontendAttrs["image-resolve-mode"] = pb.AttrImageResolveModeForcePull
|
||||
} else if nodeDriver.IsMobyDriver() {
|
||||
// moby driver always resolves local images by default
|
||||
so.FrontendAttrs["image-resolve-mode"] = pb.AttrImageResolveModePreferLocal
|
||||
}
|
||||
if opt.Target != "" {
|
||||
so.FrontendAttrs["target"] = opt.Target
|
||||
}
|
||||
if len(opt.NoCacheFilter) > 0 {
|
||||
so.FrontendAttrs["no-cache"] = strings.Join(opt.NoCacheFilter, ",")
|
||||
}
|
||||
if opt.NoCache {
|
||||
so.FrontendAttrs["no-cache"] = ""
|
||||
}
|
||||
for k, v := range opt.BuildArgs {
|
||||
so.FrontendAttrs["build-arg:"+k] = v
|
||||
}
|
||||
for k, v := range opt.Labels {
|
||||
so.FrontendAttrs["label:"+k] = v
|
||||
}
|
||||
|
||||
for k, v := range node.ProxyConfig {
|
||||
if _, ok := opt.BuildArgs[k]; !ok {
|
||||
so.FrontendAttrs["build-arg:"+k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// set platforms
|
||||
if len(opt.Platforms) != 0 {
|
||||
pp := make([]string, len(opt.Platforms))
|
||||
for i, p := range opt.Platforms {
|
||||
pp[i] = platforms.Format(p)
|
||||
}
|
||||
if len(pp) > 1 && !nodeDriver.Features(ctx)[driver.MultiPlatform] {
|
||||
return nil, nil, notSupported(driver.MultiPlatform, nodeDriver, "https://docs.docker.com/go/build-multi-platform/")
|
||||
}
|
||||
so.FrontendAttrs["platform"] = strings.Join(pp, ",")
|
||||
}
|
||||
|
||||
// setup networkmode
|
||||
switch opt.NetworkMode {
|
||||
case "host":
|
||||
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
|
||||
so.AllowedEntitlements = append(so.AllowedEntitlements, entitlements.EntitlementNetworkHost)
|
||||
case "none":
|
||||
so.FrontendAttrs["force-network-mode"] = opt.NetworkMode
|
||||
case "", "default":
|
||||
default:
|
||||
return nil, nil, errors.Errorf("network mode %q not supported by buildkit - you can define a custom network for your builder using the network driver-opt in buildx create", opt.NetworkMode)
|
||||
}
|
||||
|
||||
// setup extrahosts
|
||||
extraHosts, err := toBuildkitExtraHosts(ctx, opt.ExtraHosts, nodeDriver)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(extraHosts) > 0 {
|
||||
so.FrontendAttrs["add-hosts"] = extraHosts
|
||||
}
|
||||
|
||||
// setup shm size
|
||||
if opt.ShmSize.Value() > 0 {
|
||||
so.FrontendAttrs["shm-size"] = strconv.FormatInt(opt.ShmSize.Value(), 10)
|
||||
}
|
||||
|
||||
// setup ulimits
|
||||
ulimits, err := toBuildkitUlimits(opt.Ulimits)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
} else if len(ulimits) > 0 {
|
||||
so.FrontendAttrs["ulimit"] = ulimits
|
||||
}
|
||||
|
||||
return &so, releaseF, nil
|
||||
}
|
||||
|
||||
func loadInputs(ctx context.Context, d *driver.DriverHandle, inp Inputs, addVCSLocalDir func(key, dir string, so *client.SolveOpt), pw progress.Writer, target *client.SolveOpt) (func(), error) {
|
||||
if inp.ContextPath == "" {
|
||||
return nil, errors.New("please specify build context (e.g. \".\" for the current directory)")
|
||||
}
|
||||
|
||||
// TODO: handle stdin, symlinks, remote contexts, check files exist
|
||||
|
||||
var (
|
||||
err error
|
||||
dockerfileReader io.Reader
|
||||
dockerfileDir string
|
||||
dockerfileName = inp.DockerfilePath
|
||||
toRemove []string
|
||||
)
|
||||
|
||||
switch {
|
||||
case inp.ContextState != nil:
|
||||
if target.FrontendInputs == nil {
|
||||
target.FrontendInputs = make(map[string]llb.State)
|
||||
}
|
||||
target.FrontendInputs["context"] = *inp.ContextState
|
||||
target.FrontendInputs["dockerfile"] = *inp.ContextState
|
||||
case inp.ContextPath == "-":
|
||||
if inp.DockerfilePath == "-" {
|
||||
return nil, errStdinConflict
|
||||
}
|
||||
|
||||
buf := bufio.NewReader(inp.InStream)
|
||||
magic, err := buf.Peek(archiveHeaderSize * 2)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, errors.Wrap(err, "failed to peek context header from STDIN")
|
||||
}
|
||||
if !(err == io.EOF && len(magic) == 0) {
|
||||
if isArchive(magic) {
|
||||
// stdin is context
|
||||
up := uploadprovider.New()
|
||||
target.FrontendAttrs["context"] = up.Add(buf)
|
||||
target.Session = append(target.Session, up)
|
||||
} else {
|
||||
if inp.DockerfilePath != "" {
|
||||
return nil, errDockerfileConflict
|
||||
}
|
||||
// stdin is dockerfile
|
||||
dockerfileReader = buf
|
||||
inp.ContextPath, _ = os.MkdirTemp("", "empty-dir")
|
||||
toRemove = append(toRemove, inp.ContextPath)
|
||||
if err := setLocalMount("context", inp.ContextPath, target, addVCSLocalDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
case osutil.IsLocalDir(inp.ContextPath):
|
||||
if err := setLocalMount("context", inp.ContextPath, target, addVCSLocalDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch inp.DockerfilePath {
|
||||
case "-":
|
||||
dockerfileReader = inp.InStream
|
||||
case "":
|
||||
dockerfileDir = inp.ContextPath
|
||||
default:
|
||||
dockerfileDir = filepath.Dir(inp.DockerfilePath)
|
||||
dockerfileName = filepath.Base(inp.DockerfilePath)
|
||||
}
|
||||
case IsRemoteURL(inp.ContextPath):
|
||||
if inp.DockerfilePath == "-" {
|
||||
dockerfileReader = inp.InStream
|
||||
} else if filepath.IsAbs(inp.DockerfilePath) {
|
||||
dockerfileDir = filepath.Dir(inp.DockerfilePath)
|
||||
dockerfileName = filepath.Base(inp.DockerfilePath)
|
||||
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
|
||||
}
|
||||
target.FrontendAttrs["context"] = inp.ContextPath
|
||||
default:
|
||||
return nil, errors.Errorf("unable to prepare context: path %q not found", inp.ContextPath)
|
||||
}
|
||||
|
||||
if inp.DockerfileInline != "" {
|
||||
dockerfileReader = strings.NewReader(inp.DockerfileInline)
|
||||
}
|
||||
|
||||
if dockerfileReader != nil {
|
||||
dockerfileDir, err = createTempDockerfile(dockerfileReader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toRemove = append(toRemove, dockerfileDir)
|
||||
dockerfileName = "Dockerfile"
|
||||
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
|
||||
}
|
||||
if urlutil.IsURL(inp.DockerfilePath) {
|
||||
dockerfileDir, err = createTempDockerfileFromURL(ctx, d, inp.DockerfilePath, pw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toRemove = append(toRemove, dockerfileDir)
|
||||
dockerfileName = "Dockerfile"
|
||||
target.FrontendAttrs["dockerfilekey"] = "dockerfile"
|
||||
delete(target.FrontendInputs, "dockerfile")
|
||||
}
|
||||
|
||||
if dockerfileName == "" {
|
||||
dockerfileName = "Dockerfile"
|
||||
}
|
||||
|
||||
if dockerfileDir != "" {
|
||||
if err := setLocalMount("dockerfile", dockerfileDir, target, addVCSLocalDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dockerfileName = handleLowercaseDockerfile(dockerfileDir, dockerfileName)
|
||||
}
|
||||
|
||||
target.FrontendAttrs["filename"] = dockerfileName
|
||||
|
||||
for k, v := range inp.NamedContexts {
|
||||
target.FrontendAttrs["frontend.caps"] = "moby.buildkit.frontend.contexts+forward"
|
||||
if v.State != nil {
|
||||
target.FrontendAttrs["context:"+k] = "input:" + k
|
||||
if target.FrontendInputs == nil {
|
||||
target.FrontendInputs = make(map[string]llb.State)
|
||||
}
|
||||
target.FrontendInputs[k] = *v.State
|
||||
continue
|
||||
}
|
||||
|
||||
if IsRemoteURL(v.Path) || strings.HasPrefix(v.Path, "docker-image://") || strings.HasPrefix(v.Path, "target:") {
|
||||
target.FrontendAttrs["context:"+k] = v.Path
|
||||
continue
|
||||
}
|
||||
|
||||
// handle OCI layout
|
||||
if strings.HasPrefix(v.Path, "oci-layout://") {
|
||||
pathAlone := strings.TrimPrefix(v.Path, "oci-layout://")
|
||||
localPath := pathAlone
|
||||
localPath, dig, hasDigest := strings.Cut(localPath, "@")
|
||||
localPath, tag, hasTag := strings.Cut(localPath, ":")
|
||||
if !hasTag {
|
||||
tag = "latest"
|
||||
hasTag = true
|
||||
}
|
||||
idx := ociindex.NewStoreIndex(localPath)
|
||||
if !hasDigest {
|
||||
// lookup by name
|
||||
desc, err := idx.Get(tag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if desc != nil {
|
||||
dig = string(desc.Digest)
|
||||
hasDigest = true
|
||||
}
|
||||
}
|
||||
if !hasDigest {
|
||||
// lookup single
|
||||
desc, err := idx.GetSingle()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if desc != nil {
|
||||
dig = string(desc.Digest)
|
||||
hasDigest = true
|
||||
}
|
||||
}
|
||||
if !hasDigest {
|
||||
return nil, errors.Errorf("oci-layout reference %q could not be resolved", v.Path)
|
||||
}
|
||||
_, err := digest.Parse(dig)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid oci-layout digest %s", dig)
|
||||
}
|
||||
|
||||
store, err := local.NewStore(localPath)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "invalid store at %s", localPath)
|
||||
}
|
||||
storeName := identity.NewID()
|
||||
if target.OCIStores == nil {
|
||||
target.OCIStores = map[string]content.Store{}
|
||||
}
|
||||
target.OCIStores[storeName] = store
|
||||
|
||||
layout := "oci-layout://" + storeName
|
||||
if hasTag {
|
||||
layout += ":" + tag
|
||||
}
|
||||
if hasDigest {
|
||||
layout += "@" + dig
|
||||
}
|
||||
|
||||
target.FrontendAttrs["context:"+k] = layout
|
||||
continue
|
||||
}
|
||||
st, err := os.Stat(v.Path)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get build context %v", k)
|
||||
}
|
||||
if !st.IsDir() {
|
||||
return nil, errors.Wrapf(syscall.ENOTDIR, "failed to get build context path %v", v)
|
||||
}
|
||||
localName := k
|
||||
if k == "context" || k == "dockerfile" {
|
||||
localName = "_" + k // underscore to avoid collisions
|
||||
}
|
||||
if err := setLocalMount(localName, v.Path, target, addVCSLocalDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target.FrontendAttrs["context:"+k] = "local:" + localName
|
||||
}
|
||||
|
||||
release := func() {
|
||||
for _, dir := range toRemove {
|
||||
os.RemoveAll(dir)
|
||||
}
|
||||
}
|
||||
return release, nil
|
||||
}
|
||||
|
||||
func setLocalMount(name, root string, so *client.SolveOpt, addVCSLocalDir func(key, dir string, so *client.SolveOpt)) error {
|
||||
lm, err := fsutil.NewFS(root)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
root, err = filepath.EvalSymlinks(root) // keep same behavior as fsutil.NewFS
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if so.LocalMounts == nil {
|
||||
so.LocalMounts = map[string]fsutil.FS{}
|
||||
}
|
||||
so.LocalMounts[name] = lm
|
||||
if addVCSLocalDir != nil {
|
||||
addVCSLocalDir(name, root, so)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTempDockerfile(r io.Reader) (string, error) {
|
||||
dir, err := os.MkdirTemp("", "dockerfile")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
f, err := os.Create(filepath.Join(dir, "Dockerfile"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(f, r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return dir, err
|
||||
}
|
||||
|
||||
// handle https://github.com/moby/moby/pull/10858
|
||||
func handleLowercaseDockerfile(dir, p string) string {
|
||||
if filepath.Base(p) != "Dockerfile" {
|
||||
return p
|
||||
}
|
||||
|
||||
f, err := os.Open(filepath.Dir(filepath.Join(dir, p)))
|
||||
if err != nil {
|
||||
return p
|
||||
}
|
||||
|
||||
names, err := f.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return p
|
||||
}
|
||||
|
||||
foundLowerCase := false
|
||||
for _, n := range names {
|
||||
if n == "Dockerfile" {
|
||||
return p
|
||||
}
|
||||
if n == "dockerfile" {
|
||||
foundLowerCase = true
|
||||
}
|
||||
}
|
||||
if foundLowerCase {
|
||||
return filepath.Join(filepath.Dir(p), "dockerfile")
|
||||
}
|
||||
return p
|
||||
}
|
||||
157
build/provenance.go
Normal file
157
build/provenance.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/content/proxy"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
controlapi "github.com/moby/buildkit/api/services/control"
|
||||
"github.com/moby/buildkit/client"
|
||||
provenancetypes "github.com/moby/buildkit/solver/llbsolver/provenance/types"
|
||||
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type provenancePredicate struct {
|
||||
Builder *provenanceBuilder `json:"builder,omitempty"`
|
||||
provenancetypes.ProvenancePredicate
|
||||
}
|
||||
|
||||
type provenanceBuilder struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
}
|
||||
|
||||
func setRecordProvenance(ctx context.Context, c *client.Client, sr *client.SolveResponse, ref string, pw progress.Writer) error {
|
||||
mode := confutil.MetadataProvenance()
|
||||
if mode == confutil.MetadataProvenanceModeDisabled {
|
||||
return nil
|
||||
}
|
||||
pw = progress.ResetTime(pw)
|
||||
return progress.Wrap("resolving provenance for metadata file", pw.Write, func(l progress.SubLogger) error {
|
||||
res, err := fetchProvenance(ctx, c, ref, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for k, v := range res {
|
||||
sr.ExporterResponse[k] = v
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func fetchProvenance(ctx context.Context, c *client.Client, ref string, mode confutil.MetadataProvenanceMode) (out map[string]string, err error) {
|
||||
cl, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
|
||||
Ref: ref,
|
||||
EarlyExit: true,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mu sync.Mutex
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
store := proxy.NewContentStore(c.ContentClient())
|
||||
for {
|
||||
ev, err := cl.Recv()
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ev.Record == nil {
|
||||
continue
|
||||
}
|
||||
if ev.Record.Result != nil {
|
||||
desc := lookupProvenance(ev.Record.Result)
|
||||
if desc == nil {
|
||||
continue
|
||||
}
|
||||
eg.Go(func() error {
|
||||
dt, err := content.ReadBlob(ctx, store, *desc)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to load provenance blob from build record")
|
||||
}
|
||||
prv, err := encodeProvenance(dt, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mu.Lock()
|
||||
if out == nil {
|
||||
out = make(map[string]string)
|
||||
}
|
||||
out["buildx.build.provenance"] = prv
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
} else if ev.Record.Results != nil {
|
||||
for platform, res := range ev.Record.Results {
|
||||
platform := platform
|
||||
desc := lookupProvenance(res)
|
||||
if desc == nil {
|
||||
continue
|
||||
}
|
||||
eg.Go(func() error {
|
||||
dt, err := content.ReadBlob(ctx, store, *desc)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to load provenance blob from build record")
|
||||
}
|
||||
prv, err := encodeProvenance(dt, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mu.Lock()
|
||||
if out == nil {
|
||||
out = make(map[string]string)
|
||||
}
|
||||
out["buildx.build.provenance/"+platform] = prv
|
||||
mu.Unlock()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return out, eg.Wait()
|
||||
}
|
||||
|
||||
func lookupProvenance(res *controlapi.BuildResultInfo) *ocispecs.Descriptor {
|
||||
for _, a := range res.Attestations {
|
||||
if a.MediaType == "application/vnd.in-toto+json" && strings.HasPrefix(a.Annotations["in-toto.io/predicate-type"], "https://slsa.dev/provenance/") {
|
||||
return &ocispecs.Descriptor{
|
||||
Digest: a.Digest,
|
||||
Size: a.Size_,
|
||||
MediaType: a.MediaType,
|
||||
Annotations: a.Annotations,
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeProvenance(dt []byte, mode confutil.MetadataProvenanceMode) (string, error) {
|
||||
var prv provenancePredicate
|
||||
if err := json.Unmarshal(dt, &prv); err != nil {
|
||||
return "", errors.Wrapf(err, "failed to unmarshal provenance")
|
||||
}
|
||||
if prv.Builder != nil && prv.Builder.ID == "" {
|
||||
// reset builder if id is empty
|
||||
prv.Builder = nil
|
||||
}
|
||||
if mode == confutil.MetadataProvenanceModeMin {
|
||||
// reset fields for minimal provenance
|
||||
prv.BuildConfig = nil
|
||||
prv.Metadata = nil
|
||||
}
|
||||
dtprv, err := json.Marshal(prv)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to marshal provenance")
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(dtprv), nil
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func NewResultHandle(ctx context.Context, cc *client.Client, opt client.SolveOpt
|
||||
gwClient: c,
|
||||
gwCtx: ctx,
|
||||
}
|
||||
respErr = se
|
||||
respErr = err // return original error to preserve stacktrace
|
||||
close(done)
|
||||
|
||||
// Block until the caller closes the ResultHandle.
|
||||
@@ -160,6 +160,7 @@ func NewResultHandle(ctx context.Context, cc *client.Client, opt client.SolveOpt
|
||||
opt.Ref = ""
|
||||
opt.Exports = nil
|
||||
opt.CacheExports = nil
|
||||
opt.Internal = true
|
||||
_, respErr = cc.Build(ctx, opt, "buildx", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
|
||||
res, err := evalDefinition(ctx, c, def)
|
||||
if err != nil {
|
||||
@@ -387,7 +388,7 @@ func populateProcessConfigFromResult(req *gateway.StartRequest, res *gateway.Res
|
||||
} else if img != nil {
|
||||
args = append(args, img.Config.Entrypoint...)
|
||||
}
|
||||
if cfg.Cmd != nil {
|
||||
if !cfg.NoCmd {
|
||||
args = append(args, cfg.Cmd...)
|
||||
} else if img != nil {
|
||||
args = append(args, img.Config.Cmd...)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func createTempDockerfileFromURL(ctx context.Context, d driver.Driver, url string, pw progress.Writer) (string, error) {
|
||||
func createTempDockerfileFromURL(ctx context.Context, d *driver.DriverHandle, url string, pw progress.Writer) (string, error) {
|
||||
c, err := driver.Boot(ctx, ctx, d, pw)
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -21,7 +21,7 @@ func createTempDockerfileFromURL(ctx context.Context, d driver.Driver, url strin
|
||||
var out string
|
||||
ch, done := progress.NewChannel(pw)
|
||||
defer func() { <-done }()
|
||||
_, err = c.Build(ctx, client.SolveOpt{}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
|
||||
_, err = c.Build(ctx, client.SolveOpt{Internal: true}, "buildx", func(ctx context.Context, c gwclient.Client) (*gwclient.Result, error) {
|
||||
def, err := llb.HTTP(url, llb.Filename("Dockerfile"), llb.WithCustomNamef("[internal] load %s", url)).Marshal(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -3,14 +3,18 @@ package build
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"github.com/moby/buildkit/util/gitutil"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -32,11 +36,6 @@ func IsRemoteURL(c string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func isLocalDir(c string) bool {
|
||||
st, err := os.Stat(c)
|
||||
return err == nil && st.IsDir()
|
||||
}
|
||||
|
||||
func isArchive(header []byte) bool {
|
||||
for _, m := range [][]byte{
|
||||
{0x42, 0x5A, 0x68}, // bzip2
|
||||
@@ -57,18 +56,34 @@ func isArchive(header []byte) bool {
|
||||
}
|
||||
|
||||
// toBuildkitExtraHosts converts hosts from docker key:value format to buildkit's csv format
|
||||
func toBuildkitExtraHosts(inp []string, mobyDriver bool) (string, error) {
|
||||
func toBuildkitExtraHosts(ctx context.Context, inp []string, nodeDriver *driver.DriverHandle) (string, error) {
|
||||
if len(inp) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
hosts := make([]string, 0, len(inp))
|
||||
for _, h := range inp {
|
||||
host, ip, ok := strings.Cut(h, ":")
|
||||
host, ip, ok := strings.Cut(h, "=")
|
||||
if !ok {
|
||||
host, ip, ok = strings.Cut(h, ":")
|
||||
}
|
||||
if !ok || host == "" || ip == "" {
|
||||
return "", errors.Errorf("invalid host %s", h)
|
||||
}
|
||||
// Skip IP address validation for "host-gateway" string with moby driver
|
||||
if !mobyDriver || ip != mobyHostGatewayName {
|
||||
// If the IP Address is a "host-gateway", replace this value with the
|
||||
// IP address provided by the worker's label.
|
||||
if ip == mobyHostGatewayName {
|
||||
hgip, err := nodeDriver.HostGatewayIP(ctx)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "unable to derive the IP value for host-gateway")
|
||||
}
|
||||
ip = hgip.String()
|
||||
} else {
|
||||
// If the address is enclosed in square brackets, extract it (for IPv6, but
|
||||
// permit it for IPv4 as well; we don't know the address family here, but it's
|
||||
// unambiguous).
|
||||
if len(ip) > 2 && ip[0] == '[' && ip[len(ip)-1] == ']' {
|
||||
ip = ip[1 : len(ip)-1]
|
||||
}
|
||||
if net.ParseIP(ip) == nil {
|
||||
return "", errors.Errorf("invalid host %s", h)
|
||||
}
|
||||
@@ -89,3 +104,21 @@ func toBuildkitUlimits(inp *opts.UlimitOpt) (string, error) {
|
||||
}
|
||||
return strings.Join(ulimits, ","), nil
|
||||
}
|
||||
|
||||
func notSupported(f driver.Feature, d *driver.DriverHandle, docs string) error {
|
||||
return errors.Errorf(`%s is not supported for the %s driver.
|
||||
Switch to a different driver, or turn on the containerd image store, and try again.
|
||||
Learn more at %s`, f, d.Factory().Name(), docs)
|
||||
}
|
||||
|
||||
func noDefaultLoad() bool {
|
||||
v, ok := os.LookupEnv("BUILDX_NO_DEFAULT_LOAD")
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
b, err := strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
logrus.Warnf("invalid non-bool value for BUILDX_NO_DEFAULT_LOAD: %s", v)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
148
build/utils_test.go
Normal file
148
build/utils_test.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package build
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestToBuildkitExtraHosts(t *testing.T) {
|
||||
tests := []struct {
|
||||
doc string
|
||||
input []string
|
||||
expectedOut string // Expect output==input if not set.
|
||||
expectedErr string // Expect success if not set.
|
||||
}{
|
||||
{
|
||||
doc: "IPv4, colon sep",
|
||||
input: []string{`myhost:192.168.0.1`},
|
||||
expectedOut: `myhost=192.168.0.1`,
|
||||
},
|
||||
{
|
||||
doc: "IPv4, eq sep",
|
||||
input: []string{`myhost=192.168.0.1`},
|
||||
},
|
||||
{
|
||||
doc: "Weird but permitted, IPv4 with brackets",
|
||||
input: []string{`myhost=[192.168.0.1]`},
|
||||
expectedOut: `myhost=192.168.0.1`,
|
||||
},
|
||||
{
|
||||
doc: "Host and domain",
|
||||
input: []string{`host.and.domain.invalid:10.0.2.1`},
|
||||
expectedOut: `host.and.domain.invalid=10.0.2.1`,
|
||||
},
|
||||
{
|
||||
doc: "IPv6, colon sep",
|
||||
input: []string{`anipv6host:2003:ab34:e::1`},
|
||||
expectedOut: `anipv6host=2003:ab34:e::1`,
|
||||
},
|
||||
{
|
||||
doc: "IPv6, colon sep, brackets",
|
||||
input: []string{`anipv6host:[2003:ab34:e::1]`},
|
||||
expectedOut: `anipv6host=2003:ab34:e::1`,
|
||||
},
|
||||
{
|
||||
doc: "IPv6, eq sep, brackets",
|
||||
input: []string{`anipv6host=[2003:ab34:e::1]`},
|
||||
expectedOut: `anipv6host=2003:ab34:e::1`,
|
||||
},
|
||||
{
|
||||
doc: "IPv6 localhost, colon sep",
|
||||
input: []string{`ipv6local:::1`},
|
||||
expectedOut: `ipv6local=::1`,
|
||||
},
|
||||
{
|
||||
doc: "IPv6 localhost, eq sep",
|
||||
input: []string{`ipv6local=::1`},
|
||||
},
|
||||
{
|
||||
doc: "IPv6 localhost, eq sep, brackets",
|
||||
input: []string{`ipv6local=[::1]`},
|
||||
expectedOut: `ipv6local=::1`,
|
||||
},
|
||||
{
|
||||
doc: "IPv6 localhost, non-canonical, colon sep",
|
||||
input: []string{`ipv6local:0:0:0:0:0:0:0:1`},
|
||||
expectedOut: `ipv6local=0:0:0:0:0:0:0:1`,
|
||||
},
|
||||
{
|
||||
doc: "IPv6 localhost, non-canonical, eq sep",
|
||||
input: []string{`ipv6local=0:0:0:0:0:0:0:1`},
|
||||
},
|
||||
{
|
||||
doc: "IPv6 localhost, non-canonical, eq sep, brackets",
|
||||
input: []string{`ipv6local=[0:0:0:0:0:0:0:1]`},
|
||||
expectedOut: `ipv6local=0:0:0:0:0:0:0:1`,
|
||||
},
|
||||
{
|
||||
doc: "Bad address, colon sep",
|
||||
input: []string{`myhost:192.notanipaddress.1`},
|
||||
expectedErr: `invalid IP address in add-host: "192.notanipaddress.1"`,
|
||||
},
|
||||
{
|
||||
doc: "Bad address, eq sep",
|
||||
input: []string{`myhost=192.notanipaddress.1`},
|
||||
expectedErr: `invalid IP address in add-host: "192.notanipaddress.1"`,
|
||||
},
|
||||
{
|
||||
doc: "No sep",
|
||||
input: []string{`thathost-nosemicolon10.0.0.1`},
|
||||
expectedErr: `bad format for add-host: "thathost-nosemicolon10.0.0.1"`,
|
||||
},
|
||||
{
|
||||
doc: "Bad IPv6",
|
||||
input: []string{`anipv6host:::::1`},
|
||||
expectedErr: `invalid IP address in add-host: "::::1"`,
|
||||
},
|
||||
{
|
||||
doc: "Bad IPv6, trailing colons",
|
||||
input: []string{`ipv6local:::0::`},
|
||||
expectedErr: `invalid IP address in add-host: "::0::"`,
|
||||
},
|
||||
{
|
||||
doc: "Bad IPv6, missing close bracket",
|
||||
input: []string{`ipv6addr=[::1`},
|
||||
expectedErr: `invalid IP address in add-host: "[::1"`,
|
||||
},
|
||||
{
|
||||
doc: "Bad IPv6, missing open bracket",
|
||||
input: []string{`ipv6addr=::1]`},
|
||||
expectedErr: `invalid IP address in add-host: "::1]"`,
|
||||
},
|
||||
{
|
||||
doc: "Missing address, colon sep",
|
||||
input: []string{`myhost.invalid:`},
|
||||
expectedErr: `invalid IP address in add-host: ""`,
|
||||
},
|
||||
{
|
||||
doc: "Missing address, eq sep",
|
||||
input: []string{`myhost.invalid=`},
|
||||
expectedErr: `invalid IP address in add-host: ""`,
|
||||
},
|
||||
{
|
||||
doc: "No input",
|
||||
input: []string{``},
|
||||
expectedErr: `bad format for add-host: ""`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
if tc.expectedOut == "" {
|
||||
tc.expectedOut = strings.Join(tc.input, ",")
|
||||
}
|
||||
t.Run(tc.doc, func(t *testing.T) {
|
||||
actualOut, actualErr := toBuildkitExtraHosts(context.TODO(), tc.input, nil)
|
||||
if tc.expectedErr == "" {
|
||||
require.Equal(t, tc.expectedOut, actualOut)
|
||||
require.Nil(t, actualErr)
|
||||
} else {
|
||||
require.Zero(t, actualOut)
|
||||
require.Error(t, actualErr, tc.expectedErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -2,18 +2,31 @@ package builder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
k8sutil "github.com/docker/buildx/driver/kubernetes/util"
|
||||
remoteutil "github.com/docker/buildx/driver/remote/util"
|
||||
"github.com/docker/buildx/localstate"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
dopts "github.com/docker/cli/opts"
|
||||
"github.com/google/shlex"
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/pflag"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@@ -157,13 +170,14 @@ func (b *Builder) Boot(ctx context.Context) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, os.Stderr, progress.PrinterModeAuto)
|
||||
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, progressui.AutoMode)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
baseCtx := ctx
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
errCh := make(chan error, len(toBoot))
|
||||
for _, idx := range toBoot {
|
||||
func(idx int) {
|
||||
eg.Go(func() error {
|
||||
@@ -171,6 +185,7 @@ func (b *Builder) Boot(ctx context.Context) (bool, error) {
|
||||
_, err := driver.Boot(ctx, baseCtx, b.nodes[idx].Driver, pw)
|
||||
if err != nil {
|
||||
b.nodes[idx].Err = err
|
||||
errCh <- err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -178,11 +193,15 @@ func (b *Builder) Boot(ctx context.Context) (bool, error) {
|
||||
}
|
||||
|
||||
err = eg.Wait()
|
||||
close(errCh)
|
||||
err1 := printer.Wait()
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
|
||||
if err == nil && len(errCh) == len(toBoot) {
|
||||
return false, <-errCh
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
@@ -207,7 +226,7 @@ type driverFactory struct {
|
||||
}
|
||||
|
||||
// Factory returns the driver factory.
|
||||
func (b *Builder) Factory(ctx context.Context) (_ driver.Factory, err error) {
|
||||
func (b *Builder) Factory(ctx context.Context, dialMeta map[string][]string) (_ driver.Factory, err error) {
|
||||
b.driverFactory.once.Do(func() {
|
||||
if b.Driver != "" {
|
||||
b.driverFactory.Factory, err = driver.GetFactory(b.Driver, true)
|
||||
@@ -230,7 +249,7 @@ func (b *Builder) Factory(ctx context.Context) (_ driver.Factory, err error) {
|
||||
if _, err = dockerapi.Ping(ctx); err != nil {
|
||||
return
|
||||
}
|
||||
b.driverFactory.Factory, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false)
|
||||
b.driverFactory.Factory, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false, dialMeta)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
@@ -240,6 +259,28 @@ func (b *Builder) Factory(ctx context.Context) (_ driver.Factory, err error) {
|
||||
return b.driverFactory.Factory, err
|
||||
}
|
||||
|
||||
func (b *Builder) MarshalJSON() ([]byte, error) {
|
||||
var berr string
|
||||
if b.err != nil {
|
||||
berr = strings.TrimSpace(b.err.Error())
|
||||
}
|
||||
return json.Marshal(struct {
|
||||
Name string
|
||||
Driver string
|
||||
LastActivity time.Time `json:",omitempty"`
|
||||
Dynamic bool
|
||||
Nodes []Node
|
||||
Err string `json:",omitempty"`
|
||||
}{
|
||||
Name: b.Name,
|
||||
Driver: b.Driver,
|
||||
LastActivity: b.LastActivity,
|
||||
Dynamic: b.Dynamic,
|
||||
Nodes: b.nodes,
|
||||
Err: berr,
|
||||
})
|
||||
}
|
||||
|
||||
// GetBuilders returns all builders
|
||||
func GetBuilders(dockerCli command.Cli, txn *store.Txn) ([]*Builder, error) {
|
||||
storeng, err := txn.List()
|
||||
@@ -290,3 +331,347 @@ func GetBuilders(dockerCli command.Cli, txn *store.Txn) ([]*Builder, error) {
|
||||
|
||||
return builders, nil
|
||||
}
|
||||
|
||||
type CreateOpts struct {
|
||||
Name string
|
||||
Driver string
|
||||
NodeName string
|
||||
Platforms []string
|
||||
BuildkitdFlags string
|
||||
BuildkitdConfigFile string
|
||||
DriverOpts []string
|
||||
Use bool
|
||||
Endpoint string
|
||||
Append bool
|
||||
}
|
||||
|
||||
func Create(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts CreateOpts) (*Builder, error) {
|
||||
var err error
|
||||
|
||||
if opts.Name == "default" {
|
||||
return nil, errors.Errorf("default is a reserved name and cannot be used to identify builder instance")
|
||||
} else if opts.Append && opts.Name == "" {
|
||||
return nil, errors.Errorf("append requires a builder name")
|
||||
}
|
||||
|
||||
name := opts.Name
|
||||
if name == "" {
|
||||
name, err = store.GenerateName(txn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !opts.Append {
|
||||
contexts, err := dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range contexts {
|
||||
if c.Name == name {
|
||||
return nil, errors.Errorf("instance name %q already exists as context builder", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ng, err := txn.NodeGroupByName(name)
|
||||
if err != nil {
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
if opts.Append && opts.Name != "" {
|
||||
return nil, errors.Errorf("failed to find instance %q for append", opts.Name)
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
buildkitHost := os.Getenv("BUILDKIT_HOST")
|
||||
|
||||
driverName := opts.Driver
|
||||
if driverName == "" {
|
||||
if ng != nil {
|
||||
driverName = ng.Driver
|
||||
} else if opts.Endpoint == "" && buildkitHost != "" {
|
||||
driverName = "remote"
|
||||
} else {
|
||||
f, err := driver.GetDefaultFactory(ctx, opts.Endpoint, dockerCli.Client(), true, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if f == nil {
|
||||
return nil, errors.Errorf("no valid drivers found")
|
||||
}
|
||||
driverName = f.Name()
|
||||
}
|
||||
}
|
||||
|
||||
if ng != nil {
|
||||
if opts.NodeName == "" && !opts.Append {
|
||||
return nil, errors.Errorf("existing instance for %q but no append mode, specify the node name to make changes for existing instances", name)
|
||||
}
|
||||
if driverName != ng.Driver {
|
||||
return nil, errors.Errorf("existing instance for %q but has mismatched driver %q", name, ng.Driver)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := driver.GetFactory(driverName, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ngOriginal := ng
|
||||
if ngOriginal != nil {
|
||||
ngOriginal = ngOriginal.Copy()
|
||||
}
|
||||
|
||||
if ng == nil {
|
||||
ng = &store.NodeGroup{
|
||||
Name: name,
|
||||
Driver: driverName,
|
||||
}
|
||||
}
|
||||
|
||||
driverOpts, err := csvToMap(opts.DriverOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buildkitdFlags, err := parseBuildkitdFlags(opts.BuildkitdFlags, driverName, driverOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ep string
|
||||
var setEp bool
|
||||
switch {
|
||||
case driverName == "kubernetes":
|
||||
if opts.Endpoint != "" {
|
||||
return nil, errors.Errorf("kubernetes driver does not support endpoint args %q", opts.Endpoint)
|
||||
}
|
||||
// generate node name if not provided to avoid duplicated endpoint
|
||||
// error: https://github.com/docker/setup-buildx-action/issues/215
|
||||
nodeName := opts.NodeName
|
||||
if nodeName == "" {
|
||||
nodeName, err = k8sutil.GenerateNodeName(name, txn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// naming endpoint to make append works
|
||||
ep = (&url.URL{
|
||||
Scheme: driverName,
|
||||
Path: "/" + name,
|
||||
RawQuery: (&url.Values{
|
||||
"deployment": {nodeName},
|
||||
"kubeconfig": {os.Getenv("KUBECONFIG")},
|
||||
}).Encode(),
|
||||
}).String()
|
||||
setEp = false
|
||||
case driverName == "remote":
|
||||
if opts.Endpoint != "" {
|
||||
ep = opts.Endpoint
|
||||
} else if buildkitHost != "" {
|
||||
ep = buildkitHost
|
||||
} else {
|
||||
return nil, errors.Errorf("no remote endpoint provided")
|
||||
}
|
||||
ep, err = validateBuildkitEndpoint(ep)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setEp = true
|
||||
case opts.Endpoint != "":
|
||||
ep, err = validateEndpoint(dockerCli, opts.Endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setEp = true
|
||||
default:
|
||||
if dockerCli.CurrentContext() == "default" && dockerCli.DockerEndpoint().TLSData != nil {
|
||||
return nil, errors.Errorf("could not create a builder instance with TLS data loaded from environment. Please use `docker context create <context-name>` to create a context for current environment and then create a builder instance with context set to <context-name>")
|
||||
}
|
||||
ep, err = dockerutil.GetCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
setEp = false
|
||||
}
|
||||
|
||||
buildkitdConfigFile := opts.BuildkitdConfigFile
|
||||
if buildkitdConfigFile == "" {
|
||||
// if buildkit daemon config is not provided, check if the default one
|
||||
// is available and use it
|
||||
if f, ok := confutil.DefaultConfigFile(dockerCli); ok {
|
||||
buildkitdConfigFile = f
|
||||
}
|
||||
}
|
||||
|
||||
if err := ng.Update(opts.NodeName, ep, opts.Platforms, setEp, opts.Append, buildkitdFlags, buildkitdConfigFile, driverOpts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := txn.Save(ng); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
b, err := New(dockerCli,
|
||||
WithName(ng.Name),
|
||||
WithStore(txn),
|
||||
WithSkippedValidation(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
nodes, err := b.LoadNodes(timeoutCtx, WithData())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
if err := node.Err; err != nil {
|
||||
err := errors.Errorf("failed to initialize builder %s (%s): %s", ng.Name, node.Name, err)
|
||||
var err2 error
|
||||
if ngOriginal == nil {
|
||||
err2 = txn.Remove(ng.Name)
|
||||
} else {
|
||||
err2 = txn.Save(ngOriginal)
|
||||
}
|
||||
if err2 != nil {
|
||||
return nil, errors.Errorf("could not rollback to previous state: %s", err2)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Use && ep != "" {
|
||||
current, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := txn.SetCurrent(current, ng.Name, false, false); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
type LeaveOpts struct {
|
||||
Name string
|
||||
NodeName string
|
||||
}
|
||||
|
||||
func Leave(ctx context.Context, txn *store.Txn, dockerCli command.Cli, opts LeaveOpts) error {
|
||||
if opts.Name == "" {
|
||||
return errors.Errorf("leave requires instance name")
|
||||
}
|
||||
if opts.NodeName == "" {
|
||||
return errors.Errorf("leave requires node name")
|
||||
}
|
||||
|
||||
ng, err := txn.NodeGroupByName(opts.Name)
|
||||
if err != nil {
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
return errors.Errorf("failed to find instance %q for leave", opts.Name)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ng.Leave(opts.NodeName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ls, err := localstate.New(confutil.ConfigDir(dockerCli))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ls.RemoveBuilderNode(ng.Name, opts.NodeName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return txn.Save(ng)
|
||||
}
|
||||
|
||||
func csvToMap(in []string) (map[string]string, error) {
|
||||
if len(in) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
m := make(map[string]string, len(in))
|
||||
for _, s := range in {
|
||||
csvReader := csv.NewReader(strings.NewReader(s))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range fields {
|
||||
p := strings.SplitN(v, "=", 2)
|
||||
if len(p) != 2 {
|
||||
return nil, errors.Errorf("invalid value %q, expecting k=v", v)
|
||||
}
|
||||
m[p[0]] = p[1]
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// validateEndpoint validates that endpoint is either a context or a docker host
|
||||
func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
|
||||
dem, err := dockerutil.GetDockerEndpoint(dockerCli, ep)
|
||||
if err == nil && dem != nil {
|
||||
if ep == "default" {
|
||||
return dem.Host, nil
|
||||
}
|
||||
return ep, nil
|
||||
}
|
||||
h, err := dopts.ParseHost(true, ep)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to parse endpoint %s", ep)
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// validateBuildkitEndpoint validates that endpoint is a valid buildkit host
|
||||
func validateBuildkitEndpoint(ep string) (string, error) {
|
||||
if err := remoteutil.IsValidEndpoint(ep); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ep, nil
|
||||
}
|
||||
|
||||
// parseBuildkitdFlags parses buildkit flags
|
||||
func parseBuildkitdFlags(inp string, driver string, driverOpts map[string]string) (res []string, err error) {
|
||||
if inp != "" {
|
||||
res, err = shlex.Split(inp)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse buildkit flags")
|
||||
}
|
||||
}
|
||||
|
||||
var allowInsecureEntitlements []string
|
||||
flags := pflag.NewFlagSet("buildkitd", pflag.ContinueOnError)
|
||||
flags.Usage = func() {}
|
||||
flags.StringArrayVar(&allowInsecureEntitlements, "allow-insecure-entitlement", nil, "")
|
||||
_ = flags.Parse(res)
|
||||
|
||||
var hasNetworkHostEntitlement bool
|
||||
for _, e := range allowInsecureEntitlements {
|
||||
if e == "network.host" {
|
||||
hasNetworkHostEntitlement = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := driverOpts["network"]; ok && v == "host" && !hasNetworkHostEntitlement && driver == "docker-container" {
|
||||
// always set network.host entitlement if user has set network=host
|
||||
res = append(res, "--allow-insecure-entitlement=network.host")
|
||||
} else if len(allowInsecureEntitlements) == 0 && (driver == "kubernetes" || driver == "docker-container") {
|
||||
// set network.host entitlement if user does not provide any as
|
||||
// network is isolated for container drivers.
|
||||
res = append(res, "--allow-insecure-entitlement=network.host")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
139
builder/builder_test.go
Normal file
139
builder/builder_test.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCsvToMap(t *testing.T) {
|
||||
d := []string{
|
||||
"\"tolerations=key=foo,value=bar;key=foo2,value=bar2\",replicas=1",
|
||||
"namespace=default",
|
||||
}
|
||||
r, err := csvToMap(d)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, r, "tolerations")
|
||||
require.Equal(t, r["tolerations"], "key=foo,value=bar;key=foo2,value=bar2")
|
||||
|
||||
require.Contains(t, r, "replicas")
|
||||
require.Equal(t, r["replicas"], "1")
|
||||
|
||||
require.Contains(t, r, "namespace")
|
||||
require.Equal(t, r["namespace"], "default")
|
||||
}
|
||||
|
||||
func TestParseBuildkitdFlags(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
flags string
|
||||
driver string
|
||||
driverOpts map[string]string
|
||||
expected []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"docker-container no flags",
|
||||
"",
|
||||
"docker-container",
|
||||
nil,
|
||||
[]string{
|
||||
"--allow-insecure-entitlement=network.host",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"kubernetes no flags",
|
||||
"",
|
||||
"kubernetes",
|
||||
nil,
|
||||
[]string{
|
||||
"--allow-insecure-entitlement=network.host",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"remote no flags",
|
||||
"",
|
||||
"remote",
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"docker-container with insecure flag",
|
||||
"--allow-insecure-entitlement=security.insecure",
|
||||
"docker-container",
|
||||
nil,
|
||||
[]string{
|
||||
"--allow-insecure-entitlement=security.insecure",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"docker-container with insecure and host flag",
|
||||
"--allow-insecure-entitlement=network.host --allow-insecure-entitlement=security.insecure",
|
||||
"docker-container",
|
||||
nil,
|
||||
[]string{
|
||||
"--allow-insecure-entitlement=network.host",
|
||||
"--allow-insecure-entitlement=security.insecure",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"docker-container with network host opt",
|
||||
"",
|
||||
"docker-container",
|
||||
map[string]string{"network": "host"},
|
||||
[]string{
|
||||
"--allow-insecure-entitlement=network.host",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"docker-container with host flag and network host opt",
|
||||
"--allow-insecure-entitlement=network.host",
|
||||
"docker-container",
|
||||
map[string]string{"network": "host"},
|
||||
[]string{
|
||||
"--allow-insecure-entitlement=network.host",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"docker-container with insecure, host flag and network host opt",
|
||||
"--allow-insecure-entitlement=network.host --allow-insecure-entitlement=security.insecure",
|
||||
"docker-container",
|
||||
map[string]string{"network": "host"},
|
||||
[]string{
|
||||
"--allow-insecure-entitlement=network.host",
|
||||
"--allow-insecure-entitlement=security.insecure",
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"error parsing flags",
|
||||
"foo'",
|
||||
"docker-container",
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
flags, err := parseBuildkitdFlags(tt.flags, tt.driver, tt.driverOpts)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, flags)
|
||||
})
|
||||
}
|
||||
}
|
||||
102
builder/node.go
102
builder/node.go
@@ -2,7 +2,11 @@ package builder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/buildx/driver"
|
||||
ctxkube "github.com/docker/buildx/driver/kubernetes/context"
|
||||
"github.com/docker/buildx/store"
|
||||
@@ -22,15 +26,18 @@ import (
|
||||
type Node struct {
|
||||
store.Node
|
||||
Builder string
|
||||
Driver driver.Driver
|
||||
Driver *driver.DriverHandle
|
||||
DriverInfo *driver.Info
|
||||
Platforms []ocispecs.Platform
|
||||
GCPolicy []client.PruneInfo
|
||||
Labels map[string]string
|
||||
ImageOpt imagetools.Opt
|
||||
ProxyConfig map[string]string
|
||||
Version string
|
||||
Err error
|
||||
|
||||
// worker settings
|
||||
IDs []string
|
||||
Platforms []ocispecs.Platform
|
||||
GCPolicy []client.PruneInfo
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
// Nodes returns nodes for this builder.
|
||||
@@ -38,9 +45,35 @@ func (b *Builder) Nodes() []Node {
|
||||
return b.nodes
|
||||
}
|
||||
|
||||
type LoadNodesOption func(*loadNodesOptions)
|
||||
|
||||
type loadNodesOptions struct {
|
||||
data bool
|
||||
dialMeta map[string][]string
|
||||
}
|
||||
|
||||
func WithData() LoadNodesOption {
|
||||
return func(o *loadNodesOptions) {
|
||||
o.data = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithDialMeta(dialMeta map[string][]string) LoadNodesOption {
|
||||
return func(o *loadNodesOptions) {
|
||||
o.dialMeta = dialMeta
|
||||
}
|
||||
}
|
||||
|
||||
// LoadNodes loads and returns nodes for this builder.
|
||||
// TODO: this should be a method on a Node object and lazy load data for each driver.
|
||||
func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err error) {
|
||||
func (b *Builder) LoadNodes(ctx context.Context, opts ...LoadNodesOption) (_ []Node, err error) {
|
||||
lno := loadNodesOptions{
|
||||
data: false,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(&lno)
|
||||
}
|
||||
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
b.nodes = make([]Node, len(b.NodeGroup.Nodes))
|
||||
|
||||
@@ -50,7 +83,7 @@ func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err e
|
||||
}
|
||||
}()
|
||||
|
||||
factory, err := b.Factory(ctx)
|
||||
factory, err := b.Factory(ctx, lno.dialMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -82,12 +115,12 @@ func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err e
|
||||
contextStore := b.opts.dockerCli.ContextStore()
|
||||
|
||||
var kcc driver.KubeClientConfig
|
||||
kcc, err = ctxkube.ConfigFromContext(n.Endpoint, contextStore)
|
||||
kcc, err = ctxkube.ConfigFromEndpoint(n.Endpoint, contextStore)
|
||||
if err != nil {
|
||||
// err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock".
|
||||
// try again with name="default".
|
||||
// FIXME(@AkihiroSuda): n should retain real context name.
|
||||
kcc, err = ctxkube.ConfigFromContext("default", contextStore)
|
||||
kcc, err = ctxkube.ConfigFromEndpoint("default", contextStore)
|
||||
if err != nil {
|
||||
logrus.Error(err)
|
||||
}
|
||||
@@ -109,7 +142,7 @@ func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err e
|
||||
}
|
||||
}
|
||||
|
||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, factory, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, b.opts.contextPathHash)
|
||||
d, err := driver.GetDriver(ctx, driver.BuilderName(n.Name), factory, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.BuildkitdFlags, n.Files, n.DriverOpts, n.Platforms, b.opts.contextPathHash, lno.dialMeta)
|
||||
if err != nil {
|
||||
node.Err = err
|
||||
return nil
|
||||
@@ -117,7 +150,7 @@ func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err e
|
||||
node.Driver = d
|
||||
node.ImageOpt = imageopt
|
||||
|
||||
if withData {
|
||||
if lno.data {
|
||||
if err := node.loadData(ctx); err != nil {
|
||||
node.Err = err
|
||||
}
|
||||
@@ -132,7 +165,7 @@ func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err e
|
||||
}
|
||||
|
||||
// TODO: This should be done in the routine loading driver data
|
||||
if withData {
|
||||
if lno.data {
|
||||
kubernetesDriverCount := 0
|
||||
for _, d := range b.nodes {
|
||||
if d.DriverInfo != nil && len(d.DriverInfo.DynamicNodes) > 0 {
|
||||
@@ -169,6 +202,51 @@ func (b *Builder) LoadNodes(ctx context.Context, withData bool) (_ []Node, err e
|
||||
return b.nodes, nil
|
||||
}
|
||||
|
||||
func (n *Node) MarshalJSON() ([]byte, error) {
|
||||
var status string
|
||||
if n.DriverInfo != nil {
|
||||
status = n.DriverInfo.Status.String()
|
||||
}
|
||||
var nerr string
|
||||
if n.Err != nil {
|
||||
status = "error"
|
||||
nerr = strings.TrimSpace(n.Err.Error())
|
||||
}
|
||||
var pp []string
|
||||
for _, p := range n.Platforms {
|
||||
pp = append(pp, platforms.Format(p))
|
||||
}
|
||||
return json.Marshal(struct {
|
||||
Name string
|
||||
Endpoint string
|
||||
BuildkitdFlags []string `json:"Flags,omitempty"`
|
||||
DriverOpts map[string]string `json:",omitempty"`
|
||||
Files map[string][]byte `json:",omitempty"`
|
||||
Status string `json:",omitempty"`
|
||||
ProxyConfig map[string]string `json:",omitempty"`
|
||||
Version string `json:",omitempty"`
|
||||
Err string `json:",omitempty"`
|
||||
IDs []string `json:",omitempty"`
|
||||
Platforms []string `json:",omitempty"`
|
||||
GCPolicy []client.PruneInfo `json:",omitempty"`
|
||||
Labels map[string]string `json:",omitempty"`
|
||||
}{
|
||||
Name: n.Name,
|
||||
Endpoint: n.Endpoint,
|
||||
BuildkitdFlags: n.BuildkitdFlags,
|
||||
DriverOpts: n.DriverOpts,
|
||||
Files: n.Files,
|
||||
Status: status,
|
||||
ProxyConfig: n.ProxyConfig,
|
||||
Version: n.Version,
|
||||
Err: nerr,
|
||||
IDs: n.IDs,
|
||||
Platforms: pp,
|
||||
GCPolicy: n.GCPolicy,
|
||||
Labels: n.Labels,
|
||||
})
|
||||
}
|
||||
|
||||
func (n *Node) loadData(ctx context.Context) error {
|
||||
if n.Driver == nil {
|
||||
return nil
|
||||
@@ -188,12 +266,14 @@ func (n *Node) loadData(ctx context.Context) error {
|
||||
return errors.Wrap(err, "listing workers")
|
||||
}
|
||||
for idx, w := range workers {
|
||||
n.IDs = append(n.IDs, w.ID)
|
||||
n.Platforms = append(n.Platforms, w.Platforms...)
|
||||
if idx == 0 {
|
||||
n.GCPolicy = w.GCPolicy
|
||||
n.Labels = w.Labels
|
||||
}
|
||||
}
|
||||
sort.Strings(n.IDs)
|
||||
n.Platforms = platformutil.Dedupe(n.Platforms)
|
||||
inf, err := driverClient.Info(ctx)
|
||||
if err != nil {
|
||||
|
||||
128
commands/bake.go
128
commands/bake.go
@@ -4,13 +4,16 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/buildx/bake"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/localstate"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/docker/buildx/util/cobrautil/completion"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
@@ -19,7 +22,8 @@ import (
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/buildx/util/tracing"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/moby/buildkit/identity"
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -37,9 +41,7 @@ type bakeOptions struct {
|
||||
exportLoad bool
|
||||
}
|
||||
|
||||
func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
|
||||
ctx, end, err := tracing.TraceCurrentCommand(ctx, "bake")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -70,12 +72,10 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
|
||||
|
||||
overrides := in.overrides
|
||||
if in.exportPush {
|
||||
if in.exportLoad {
|
||||
return errors.Errorf("push and load may not be set together at the moment")
|
||||
}
|
||||
overrides = append(overrides, "*.push=true")
|
||||
} else if in.exportLoad {
|
||||
overrides = append(overrides, "*.output=type=docker")
|
||||
}
|
||||
if in.exportLoad {
|
||||
overrides = append(overrides, "*.load=true")
|
||||
}
|
||||
if cFlags.noCache != nil {
|
||||
overrides = append(overrides, fmt.Sprintf("*.no-cache=%t", *cFlags.noCache))
|
||||
@@ -95,8 +95,6 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
|
||||
defer cancel()
|
||||
|
||||
var nodes []builder.Node
|
||||
var files []bake.File
|
||||
var inp *bake.Input
|
||||
var progressConsoleDesc, progressTextDesc string
|
||||
|
||||
// instance only needed for reading remote bake files or building
|
||||
@@ -111,7 +109,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
|
||||
if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
|
||||
return errors.Wrapf(err, "failed to update builder last activity time")
|
||||
}
|
||||
nodes, err = b.LoadNodes(ctx, false)
|
||||
nodes, err = b.LoadNodes(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -124,7 +122,8 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
|
||||
term = true
|
||||
}
|
||||
|
||||
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, cFlags.progress,
|
||||
progressMode := progressui.DisplayMode(cFlags.progress)
|
||||
printer, err := progress.NewPrinter(ctx2, os.Stderr, progressMode,
|
||||
progress.WithDesc(progressTextDesc, progressConsoleDesc),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -137,21 +136,21 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
if err == nil && cFlags.progress != progress.PrinterModeQuiet {
|
||||
if err == nil && progressMode != progressui.QuietMode && progressMode != progressui.RawJSONMode {
|
||||
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if url != "" {
|
||||
files, inp, err = bake.ReadRemoteFiles(ctx, nodes, url, in.files, printer)
|
||||
} else {
|
||||
files, err = bake.ReadLocalFiles(in.files)
|
||||
}
|
||||
files, inp, err := readBakeFiles(ctx, nodes, url, in.files, dockerCli.In(), printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
return errors.New("couldn't find a bake definition")
|
||||
}
|
||||
|
||||
tgts, grps, err := bake.ReadTargets(ctx, files, targets, overrides, map[string]string{
|
||||
// don't forget to update documentation if you add a new
|
||||
// built-in variable: docs/bake-reference.md#built-in-variables
|
||||
@@ -181,14 +180,16 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
|
||||
return err
|
||||
}
|
||||
|
||||
def := struct {
|
||||
Group map[string]*bake.Group `json:"group,omitempty"`
|
||||
Target map[string]*bake.Target `json:"target"`
|
||||
}{
|
||||
Group: grps,
|
||||
Target: tgts,
|
||||
}
|
||||
|
||||
if in.printOnly {
|
||||
dt, err := json.MarshalIndent(struct {
|
||||
Group map[string]*bake.Group `json:"group,omitempty"`
|
||||
Target map[string]*bake.Target `json:"target"`
|
||||
}{
|
||||
grps,
|
||||
tgts,
|
||||
}, "", " ")
|
||||
dt, err := json.MarshalIndent(def, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -201,6 +202,28 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions, cFlags com
|
||||
return nil
|
||||
}
|
||||
|
||||
groupRef := identity.NewID()
|
||||
var refs []string
|
||||
for k, b := range bo {
|
||||
b.Ref = identity.NewID()
|
||||
b.GroupRef = groupRef
|
||||
b.WithProvenanceResponse = len(in.metadataFile) > 0
|
||||
refs = append(refs, b.Ref)
|
||||
bo[k] = b
|
||||
}
|
||||
dt, err := json.Marshal(def)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := saveLocalStateGroup(dockerCli, groupRef, localstate.StateGroup{
|
||||
Definition: dt,
|
||||
Targets: targets,
|
||||
Inputs: overrides,
|
||||
Refs: refs,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp, err := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer)
|
||||
if err != nil {
|
||||
return wrapBuildError(err, true)
|
||||
@@ -238,7 +261,7 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
options.builder = rootOpts.builder
|
||||
options.metadataFile = cFlags.metadataFile
|
||||
// Other common flags (noCache, pull and progress) are processed in runBake function.
|
||||
return runBake(dockerCli, args, options, cFlags)
|
||||
return runBake(cmd.Context(), dockerCli, args, options, cFlags)
|
||||
},
|
||||
ValidArgsFunction: completion.BakeTargets(options.files),
|
||||
}
|
||||
@@ -257,3 +280,54 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func saveLocalStateGroup(dockerCli command.Cli, ref string, lsg localstate.StateGroup) error {
|
||||
l, err := localstate.New(confutil.ConfigDir(dockerCli))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return l.SaveGroup(ref, lsg)
|
||||
}
|
||||
|
||||
func readBakeFiles(ctx context.Context, nodes []builder.Node, url string, names []string, stdin io.Reader, pw progress.Writer) (files []bake.File, inp *bake.Input, err error) {
|
||||
var lnames []string // local
|
||||
var rnames []string // remote
|
||||
var anames []string // both
|
||||
for _, v := range names {
|
||||
if strings.HasPrefix(v, "cwd://") {
|
||||
tname := strings.TrimPrefix(v, "cwd://")
|
||||
lnames = append(lnames, tname)
|
||||
anames = append(anames, tname)
|
||||
} else {
|
||||
rnames = append(rnames, v)
|
||||
anames = append(anames, v)
|
||||
}
|
||||
}
|
||||
|
||||
if url != "" {
|
||||
var rfiles []bake.File
|
||||
rfiles, inp, err = bake.ReadRemoteFiles(ctx, nodes, url, rnames, pw)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
files = append(files, rfiles...)
|
||||
}
|
||||
|
||||
if len(lnames) > 0 || url == "" {
|
||||
var lfiles []bake.File
|
||||
progress.Wrap("[internal] load local bake definitions", pw.Write, func(sub progress.SubLogger) error {
|
||||
if url != "" {
|
||||
lfiles, err = bake.ReadLocalFiles(lnames, stdin, sub)
|
||||
} else {
|
||||
lfiles, err = bake.ReadLocalFiles(anames, stdin, sub)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
files = append(files, lfiles...)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ package commands
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/csv"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -13,10 +15,13 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/commands/debug"
|
||||
"github.com/docker/buildx/controller"
|
||||
cbuild "github.com/docker/buildx/controller/build"
|
||||
"github.com/docker/buildx/controller/control"
|
||||
@@ -26,8 +31,12 @@ import (
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/buildflags"
|
||||
"github.com/docker/buildx/util/cobrautil"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/desktop"
|
||||
"github.com/docker/buildx/util/ioset"
|
||||
"github.com/docker/buildx/util/metricutil"
|
||||
"github.com/docker/buildx/util/osutil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/buildx/util/tracing"
|
||||
"github.com/docker/cli-docs-tool/annotation"
|
||||
@@ -39,21 +48,25 @@ import (
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||
"github.com/moby/buildkit/frontend/subrequests"
|
||||
"github.com/moby/buildkit/frontend/subrequests/lint"
|
||||
"github.com/moby/buildkit/frontend/subrequests/outline"
|
||||
"github.com/moby/buildkit/frontend/subrequests/targets"
|
||||
"github.com/moby/buildkit/solver/errdefs"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/moby/buildkit/util/grpcerrors"
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/morikuni/aec"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/metric"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
type buildOptions struct {
|
||||
allow []string
|
||||
annotations []string
|
||||
buildArgs []string
|
||||
cacheFrom []string
|
||||
cacheTo []string
|
||||
@@ -76,9 +89,6 @@ type buildOptions struct {
|
||||
target string
|
||||
ulimits *dockeropts.UlimitOpt
|
||||
|
||||
invoke *invokeConfig
|
||||
noBuild bool
|
||||
|
||||
attests []string
|
||||
sbom string
|
||||
provenance string
|
||||
@@ -94,30 +104,45 @@ type buildOptions struct {
|
||||
exportLoad bool
|
||||
|
||||
control.ControlOptions
|
||||
|
||||
invokeConfig *invokeConfig
|
||||
}
|
||||
|
||||
func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error) {
|
||||
var err error
|
||||
|
||||
buildArgs, err := listToMap(o.buildArgs, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
labels, err := listToMap(o.labels, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
opts := controllerapi.BuildOptions{
|
||||
Allow: o.allow,
|
||||
BuildArgs: listToMap(o.buildArgs, true),
|
||||
CgroupParent: o.cgroupParent,
|
||||
ContextPath: o.contextPath,
|
||||
DockerfileName: o.dockerfileName,
|
||||
ExtraHosts: o.extraHosts,
|
||||
Labels: listToMap(o.labels, false),
|
||||
NetworkMode: o.networkMode,
|
||||
NoCacheFilter: o.noCacheFilter,
|
||||
Platforms: o.platforms,
|
||||
ShmSize: int64(o.shmSize),
|
||||
Tags: o.tags,
|
||||
Target: o.target,
|
||||
Ulimits: dockerUlimitToControllerUlimit(o.ulimits),
|
||||
Builder: o.builder,
|
||||
NoCache: o.noCache,
|
||||
Pull: o.pull,
|
||||
ExportPush: o.exportPush,
|
||||
ExportLoad: o.exportLoad,
|
||||
Allow: o.allow,
|
||||
Annotations: o.annotations,
|
||||
BuildArgs: buildArgs,
|
||||
CgroupParent: o.cgroupParent,
|
||||
ContextPath: o.contextPath,
|
||||
DockerfileName: o.dockerfileName,
|
||||
ExtraHosts: o.extraHosts,
|
||||
Labels: labels,
|
||||
NetworkMode: o.networkMode,
|
||||
NoCacheFilter: o.noCacheFilter,
|
||||
Platforms: o.platforms,
|
||||
ShmSize: int64(o.shmSize),
|
||||
Tags: o.tags,
|
||||
Target: o.target,
|
||||
Ulimits: dockerUlimitToControllerUlimit(o.ulimits),
|
||||
Builder: o.builder,
|
||||
NoCache: o.noCache,
|
||||
Pull: o.pull,
|
||||
ExportPush: o.exportPush,
|
||||
ExportLoad: o.exportLoad,
|
||||
WithProvenanceResponse: len(o.metadataFile) > 0,
|
||||
}
|
||||
|
||||
// TODO: extract env var parsing to a method easily usable by library consumers
|
||||
@@ -185,24 +210,67 @@ func (o *buildOptions) toControllerOptions() (*controllerapi.BuildOptions, error
|
||||
return &opts, nil
|
||||
}
|
||||
|
||||
func (o *buildOptions) toProgress() (string, error) {
|
||||
switch o.progress {
|
||||
case progress.PrinterModeAuto, progress.PrinterModeTty, progress.PrinterModePlain, progress.PrinterModeQuiet:
|
||||
default:
|
||||
return "", errors.Errorf("progress=%s is not a valid progress option", o.progress)
|
||||
}
|
||||
|
||||
func (o *buildOptions) toDisplayMode() (progressui.DisplayMode, error) {
|
||||
progress := progressui.DisplayMode(o.progress)
|
||||
if o.quiet {
|
||||
if o.progress != progress.PrinterModeAuto && o.progress != progress.PrinterModeQuiet {
|
||||
if progress != progressui.AutoMode && progress != progressui.QuietMode {
|
||||
return "", errors.Errorf("progress=%s and quiet cannot be used together", o.progress)
|
||||
}
|
||||
return progress.PrinterModeQuiet, nil
|
||||
return progressui.QuietMode, nil
|
||||
}
|
||||
return o.progress, nil
|
||||
return progress, nil
|
||||
}
|
||||
|
||||
func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
||||
ctx := appcontext.Context()
|
||||
func buildMetricAttributes(dockerCli command.Cli, b *builder.Builder, options *buildOptions) attribute.Set {
|
||||
return attribute.NewSet(
|
||||
attribute.String("command.name", "build"),
|
||||
attribute.Stringer("command.options.hash", &buildOptionsHash{
|
||||
buildOptions: options,
|
||||
configDir: confutil.ConfigDir(dockerCli),
|
||||
}),
|
||||
attribute.String("driver.name", options.builder),
|
||||
attribute.String("driver.type", b.Driver),
|
||||
)
|
||||
}
|
||||
|
||||
// buildOptionsHash computes a hash for the buildOptions when the String method is invoked.
|
||||
// This is done so we can delay the computation of the hash until needed by OTEL using
|
||||
// the fmt.Stringer interface.
|
||||
type buildOptionsHash struct {
|
||||
*buildOptions
|
||||
configDir string
|
||||
result string
|
||||
resultOnce sync.Once
|
||||
}
|
||||
|
||||
func (o *buildOptionsHash) String() string {
|
||||
o.resultOnce.Do(func() {
|
||||
target := o.target
|
||||
contextPath := o.contextPath
|
||||
dockerfile := o.dockerfileName
|
||||
if dockerfile == "" {
|
||||
dockerfile = "Dockerfile"
|
||||
}
|
||||
|
||||
if contextPath != "-" && osutil.IsLocalDir(contextPath) {
|
||||
contextPath = osutil.ToAbs(contextPath)
|
||||
}
|
||||
salt := confutil.TryNodeIdentifier(o.configDir)
|
||||
|
||||
h := sha256.New()
|
||||
for _, s := range []string{target, contextPath, dockerfile, salt} {
|
||||
_, _ = io.WriteString(h, s)
|
||||
h.Write([]byte{0})
|
||||
}
|
||||
o.result = hex.EncodeToString(h.Sum(nil))
|
||||
})
|
||||
return o.result
|
||||
}
|
||||
|
||||
func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions) (err error) {
|
||||
mp := dockerCli.MeterProvider(ctx)
|
||||
defer metricutil.Shutdown(ctx, mp)
|
||||
|
||||
ctx, end, err := tracing.TraceCurrentCommand(ctx, "build")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -234,7 +302,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = b.LoadNodes(ctx, false)
|
||||
_, err = b.LoadNodes(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -243,19 +311,21 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
||||
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
|
||||
term = true
|
||||
}
|
||||
attributes := buildMetricAttributes(dockerCli, b, &options)
|
||||
|
||||
ctx2, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
progressMode, err := options.toProgress()
|
||||
progressMode, err := options.toDisplayMode()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var printer *progress.Printer
|
||||
printer, err = progress.NewPrinter(ctx2, os.Stderr, os.Stderr, progressMode,
|
||||
printer, err = progress.NewPrinter(ctx2, os.Stderr, progressMode,
|
||||
progress.WithDesc(
|
||||
fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver),
|
||||
fmt.Sprintf("%s:%s", b.Driver, b.Name),
|
||||
),
|
||||
progress.WithMetrics(mp, attributes),
|
||||
progress.WithOnClose(func() {
|
||||
printWarnings(os.Stderr, printer.Warnings(), progressMode)
|
||||
}),
|
||||
@@ -264,9 +334,10 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
done := timeBuildCommand(mp, attributes)
|
||||
var resp *client.SolveResponse
|
||||
var retErr error
|
||||
if isExperimental() {
|
||||
if confutil.IsExperimental() {
|
||||
resp, retErr = runControllerBuild(ctx, dockerCli, opts, options, printer)
|
||||
} else {
|
||||
resp, retErr = runBasicBuild(ctx, dockerCli, opts, options, printer)
|
||||
@@ -275,14 +346,19 @@ func runBuild(dockerCli command.Cli, options buildOptions) (err error) {
|
||||
if err := printer.Wait(); retErr == nil {
|
||||
retErr = err
|
||||
}
|
||||
|
||||
done(retErr)
|
||||
if retErr != nil {
|
||||
return retErr
|
||||
}
|
||||
|
||||
if progressMode != progress.PrinterModeQuiet {
|
||||
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
|
||||
} else {
|
||||
switch progressMode {
|
||||
case progressui.RawJSONMode:
|
||||
// no additional display
|
||||
case progressui.QuietMode:
|
||||
fmt.Println(getImageID(resp.ExporterResponse))
|
||||
default:
|
||||
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
|
||||
}
|
||||
if options.imageIDFile != "" {
|
||||
if err := os.WriteFile(options.imageIDFile, []byte(getImageID(resp.ExporterResponse)), 0644); err != nil {
|
||||
@@ -312,7 +388,7 @@ func getImageID(resp map[string]string) string {
|
||||
}
|
||||
|
||||
func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, options buildOptions, printer *progress.Printer) (*client.SolveResponse, error) {
|
||||
resp, res, err := cbuild.RunBuild(ctx, dockerCli, *opts, os.Stdin, printer, false)
|
||||
resp, res, err := cbuild.RunBuild(ctx, dockerCli, *opts, dockerCli.In(), printer, false)
|
||||
if res != nil {
|
||||
res.Done()
|
||||
}
|
||||
@@ -320,11 +396,10 @@ func runBasicBuild(ctx context.Context, dockerCli command.Cli, opts *controllera
|
||||
}
|
||||
|
||||
func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *controllerapi.BuildOptions, options buildOptions, printer *progress.Printer) (*client.SolveResponse, error) {
|
||||
if options.invoke != nil && (options.dockerfileName == "-" || options.contextPath == "-") {
|
||||
if options.invokeConfig != nil && (options.dockerfileName == "-" || options.contextPath == "-") {
|
||||
// stdin must be usable for monitor
|
||||
return nil, errors.Errorf("Dockerfile or context from stdin is not supported with invoke")
|
||||
}
|
||||
|
||||
c, err := controller.NewController(ctx, options.ControlOptions, dockerCli, printer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -345,28 +420,36 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro
|
||||
var ref string
|
||||
var retErr error
|
||||
var resp *client.SolveResponse
|
||||
f := ioset.NewSingleForwarder()
|
||||
f.SetReader(os.Stdin)
|
||||
if !options.noBuild {
|
||||
pr, pw := io.Pipe()
|
||||
|
||||
var f *ioset.SingleForwarder
|
||||
var pr io.ReadCloser
|
||||
var pw io.WriteCloser
|
||||
if options.invokeConfig == nil {
|
||||
pr = dockerCli.In()
|
||||
} else {
|
||||
f = ioset.NewSingleForwarder()
|
||||
f.SetReader(dockerCli.In())
|
||||
pr, pw = io.Pipe()
|
||||
f.SetWriter(pw, func() io.WriteCloser {
|
||||
pw.Close() // propagate EOF
|
||||
logrus.Debug("propagating stdin close")
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
ref, resp, err = c.Build(ctx, *opts, pr, printer)
|
||||
if err != nil {
|
||||
var be *controllererrors.BuildError
|
||||
if errors.As(err, &be) {
|
||||
ref = be.Ref
|
||||
retErr = err
|
||||
// We can proceed to monitor
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "failed to build")
|
||||
}
|
||||
ref, resp, err = c.Build(ctx, *opts, pr, printer)
|
||||
if err != nil {
|
||||
var be *controllererrors.BuildError
|
||||
if errors.As(err, &be) {
|
||||
ref = be.Ref
|
||||
retErr = err
|
||||
// We can proceed to monitor
|
||||
} else {
|
||||
return nil, errors.Wrapf(err, "failed to build")
|
||||
}
|
||||
}
|
||||
|
||||
if options.invokeConfig != nil {
|
||||
if err := pw.Close(); err != nil {
|
||||
logrus.Debug("failed to close stdin pipe writer")
|
||||
}
|
||||
@@ -375,28 +458,28 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro
|
||||
}
|
||||
}
|
||||
|
||||
// post-build operations
|
||||
if options.invoke != nil && options.invoke.needsMonitor(retErr) {
|
||||
if options.invokeConfig != nil && options.invokeConfig.needsDebug(retErr) {
|
||||
// Print errors before launching monitor
|
||||
if err := printError(retErr, printer); err != nil {
|
||||
logrus.Warnf("failed to print error information: %v", err)
|
||||
}
|
||||
|
||||
pr2, pw2 := io.Pipe()
|
||||
f.SetWriter(pw2, func() io.WriteCloser {
|
||||
pw2.Close() // propagate EOF
|
||||
return nil
|
||||
})
|
||||
con := console.Current()
|
||||
if err := con.SetRaw(); err != nil {
|
||||
if err := c.Disconnect(ctx, ref); err != nil {
|
||||
logrus.Warnf("disconnect error: %v", err)
|
||||
}
|
||||
return nil, errors.Errorf("failed to configure terminal: %v", err)
|
||||
}
|
||||
err = monitor.RunMonitor(ctx, ref, opts, options.invoke.InvokeConfig, c, pr2, os.Stdout, os.Stderr, printer)
|
||||
con.Reset()
|
||||
monitorBuildResult, err := options.invokeConfig.runDebug(ctx, ref, opts, c, pr2, os.Stdout, os.Stderr, printer)
|
||||
if err := pw2.Close(); err != nil {
|
||||
logrus.Debug("failed to close monitor stdin pipe reader")
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Warnf("failed to run monitor: %v", err)
|
||||
}
|
||||
if monitorBuildResult != nil {
|
||||
// Update return values with the last build result from monitor
|
||||
resp, retErr = monitorBuildResult.Resp, monitorBuildResult.Err
|
||||
}
|
||||
} else {
|
||||
if err := c.Disconnect(ctx, ref); err != nil {
|
||||
logrus.Warnf("disconnect error: %v", err)
|
||||
@@ -406,10 +489,37 @@ func runControllerBuild(ctx context.Context, dockerCli command.Cli, opts *contro
|
||||
return resp, retErr
|
||||
}
|
||||
|
||||
func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
options := buildOptions{}
|
||||
func printError(err error, printer *progress.Printer) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if err := printer.Pause(); err != nil {
|
||||
return err
|
||||
}
|
||||
defer printer.Unpause()
|
||||
for _, s := range errdefs.Sources(err) {
|
||||
s.Print(os.Stderr)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %v\n", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDebuggableBuild(dockerCli command.Cli, rootOpts *rootOptions) debug.DebuggableCmd {
|
||||
return &debuggableBuild{dockerCli: dockerCli, rootOpts: rootOpts}
|
||||
}
|
||||
|
||||
type debuggableBuild struct {
|
||||
dockerCli command.Cli
|
||||
rootOpts *rootOptions
|
||||
}
|
||||
|
||||
func (b *debuggableBuild) NewDebugger(cfg *debug.DebugConfig) *cobra.Command {
|
||||
return buildCmd(b.dockerCli, b.rootOpts, cfg)
|
||||
}
|
||||
|
||||
func buildCmd(dockerCli command.Cli, rootOpts *rootOptions, debugConfig *debug.DebugConfig) *cobra.Command {
|
||||
cFlags := &commonFlags{}
|
||||
var invokeFlag string
|
||||
options := &buildOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "build [OPTIONS] PATH | URL | -",
|
||||
@@ -431,15 +541,15 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
options.progress = cFlags.progress
|
||||
cmd.Flags().VisitAll(checkWarnedFlags)
|
||||
|
||||
if invokeFlag != "" {
|
||||
invoke, err := parseInvokeConfig(invokeFlag)
|
||||
if err != nil {
|
||||
if debugConfig != nil && (debugConfig.InvokeFlag != "" || debugConfig.OnFlag != "") {
|
||||
iConfig := new(invokeConfig)
|
||||
if err := iConfig.parseInvokeConfig(debugConfig.InvokeFlag, debugConfig.OnFlag); err != nil {
|
||||
return err
|
||||
}
|
||||
options.invoke = &invoke
|
||||
options.noBuild = invokeFlag == "debug-shell"
|
||||
options.invokeConfig = iConfig
|
||||
}
|
||||
return runBuild(dockerCli, options)
|
||||
|
||||
return runBuild(cmd.Context(), dockerCli, *options)
|
||||
},
|
||||
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||
return nil, cobra.ShellCompDirectiveFilterDirs
|
||||
@@ -454,25 +564,27 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringSliceVar(&options.extraHosts, "add-host", []string{}, `Add a custom host-to-IP mapping (format: "host:ip")`)
|
||||
flags.SetAnnotation("add-host", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#add-host"})
|
||||
flags.SetAnnotation("add-host", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/image/build/#add-host"})
|
||||
|
||||
flags.StringSliceVar(&options.allow, "allow", []string{}, `Allow extra privileged entitlement (e.g., "network.host", "security.insecure")`)
|
||||
|
||||
flags.StringArrayVarP(&options.annotations, "annotation", "", []string{}, "Add annotation to the image")
|
||||
|
||||
flags.StringArrayVar(&options.buildArgs, "build-arg", []string{}, "Set build-time variables")
|
||||
|
||||
flags.StringArrayVar(&options.cacheFrom, "cache-from", []string{}, `External cache sources (e.g., "user/app:cache", "type=local,src=path/to/dir")`)
|
||||
|
||||
flags.StringArrayVar(&options.cacheTo, "cache-to", []string{}, `Cache export destinations (e.g., "user/app:cache", "type=local,dest=path/to/dir")`)
|
||||
|
||||
flags.StringVar(&options.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
|
||||
flags.SetAnnotation("cgroup-parent", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#cgroup-parent"})
|
||||
flags.StringVar(&options.cgroupParent, "cgroup-parent", "", `Set the parent cgroup for the "RUN" instructions during build`)
|
||||
flags.SetAnnotation("cgroup-parent", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/image/build/#cgroup-parent"})
|
||||
|
||||
flags.StringArrayVar(&options.contexts, "build-context", []string{}, "Additional build contexts (e.g., name=path)")
|
||||
|
||||
flags.StringVarP(&options.dockerfileName, "file", "f", "", `Name of the Dockerfile (default: "PATH/Dockerfile")`)
|
||||
flags.SetAnnotation("file", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#file"})
|
||||
flags.SetAnnotation("file", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/image/build/#file"})
|
||||
|
||||
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
||||
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to a file")
|
||||
|
||||
flags.StringArrayVar(&options.labels, "label", []string{}, "Set metadata for an image")
|
||||
|
||||
@@ -486,8 +598,9 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
|
||||
flags.StringArrayVar(&options.platforms, "platform", platformsDefault, "Set target platform for build")
|
||||
|
||||
if isExperimental() {
|
||||
flags.StringVar(&options.printFunc, "print", "", "Print result of information request (e.g., outline, targets) [experimental]")
|
||||
if confutil.IsExperimental() {
|
||||
flags.StringVar(&options.printFunc, "print", "", "Print result of information request (e.g., outline, targets)")
|
||||
cobrautil.MarkFlagsExperimental(flags, "print")
|
||||
}
|
||||
|
||||
flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--output=type=registry"`)
|
||||
@@ -496,15 +609,15 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
|
||||
flags.StringArrayVar(&options.secrets, "secret", []string{}, `Secret to expose to the build (format: "id=mysecret[,src=/local/secret]")`)
|
||||
|
||||
flags.Var(&options.shmSize, "shm-size", `Size of "/dev/shm"`)
|
||||
flags.Var(&options.shmSize, "shm-size", `Shared memory size for build containers`)
|
||||
|
||||
flags.StringArrayVar(&options.ssh, "ssh", []string{}, `SSH agent socket or keys to expose to the build (format: "default|<id>[=<socket>|<key>[,<key>]]")`)
|
||||
|
||||
flags.StringArrayVarP(&options.tags, "tag", "t", []string{}, `Name and optionally a tag (format: "name:tag")`)
|
||||
flags.SetAnnotation("tag", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#tag"})
|
||||
flags.SetAnnotation("tag", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/image/build/#tag"})
|
||||
|
||||
flags.StringVar(&options.target, "target", "", "Set the target build stage to build")
|
||||
flags.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#target"})
|
||||
flags.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/reference/cli/docker/image/build/#target"})
|
||||
|
||||
options.ulimits = dockeropts.NewUlimitOpt(nil)
|
||||
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
||||
@@ -513,11 +626,12 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--attest=type=sbom"`)
|
||||
flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--attest=type=provenance"`)
|
||||
|
||||
if isExperimental() {
|
||||
flags.StringVar(&invokeFlag, "invoke", "", "Invoke a command after the build [experimental]")
|
||||
flags.StringVar(&options.Root, "root", "", "Specify root directory of server to connect [experimental]")
|
||||
flags.BoolVar(&options.Detach, "detach", false, "Detach buildx server (supported only on linux) [experimental]")
|
||||
flags.StringVar(&options.ServerConfig, "server-config", "", "Specify buildx server config file (used only when launching new server) [experimental]")
|
||||
if confutil.IsExperimental() {
|
||||
// TODO: move this to debug command if needed
|
||||
flags.StringVar(&options.Root, "root", "", "Specify root directory of server to connect")
|
||||
flags.BoolVar(&options.Detach, "detach", false, "Detach buildx server (supported only on linux)")
|
||||
flags.StringVar(&options.ServerConfig, "server-config", "", "Specify buildx server config file (used only when launching new server)")
|
||||
cobrautil.MarkFlagsExperimental(flags, "root", "detach", "server-config")
|
||||
}
|
||||
|
||||
// hidden flags
|
||||
@@ -540,6 +654,7 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
flags.BoolVar(&ignoreBool, "squash", false, "Squash newly built layers into a single new layer")
|
||||
flags.MarkHidden("squash")
|
||||
flags.SetAnnotation("squash", "flag-warn", []string{"experimental flag squash is removed with BuildKit. You should squash inside build using a multi-stage Dockerfile for efficiency."})
|
||||
cobrautil.MarkFlagsExperimental(flags, "squash")
|
||||
|
||||
flags.StringVarP(&ignore, "memory", "m", "", "Memory limit")
|
||||
flags.MarkHidden("memory")
|
||||
@@ -584,7 +699,7 @@ func commonBuildFlags(options *commonFlags, flags *pflag.FlagSet) {
|
||||
options.noCache = flags.Bool("no-cache", false, "Do not use cache when building the image")
|
||||
flags.StringVar(&options.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`)
|
||||
options.pull = flags.Bool("pull", false, "Always attempt to pull all referenced images")
|
||||
flags.StringVar(&options.metadataFile, "metadata-file", "", "Write build result metadata to the file")
|
||||
flags.StringVar(&options.metadataFile, "metadata-file", "", "Write build result metadata to a file")
|
||||
}
|
||||
|
||||
func checkWarnedFlags(f *pflag.Flag) {
|
||||
@@ -656,14 +771,6 @@ func (w *wrapped) Unwrap() error {
|
||||
return w.err
|
||||
}
|
||||
|
||||
func isExperimental() bool {
|
||||
if v, ok := os.LookupEnv("BUILDX_EXPERIMENTAL"); ok {
|
||||
vv, _ := strconv.ParseBool(v)
|
||||
return vv
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func updateLastActivity(dockerCli command.Cli, ng *store.NodeGroup) error {
|
||||
txn, release, err := storeutil.GetStore(dockerCli)
|
||||
if err != nil {
|
||||
@@ -673,97 +780,24 @@ func updateLastActivity(dockerCli command.Cli, ng *store.NodeGroup) error {
|
||||
return txn.UpdateLastActivity(ng)
|
||||
}
|
||||
|
||||
type invokeConfig struct {
|
||||
controllerapi.InvokeConfig
|
||||
invokeFlag string
|
||||
}
|
||||
|
||||
func (cfg *invokeConfig) needsMonitor(retErr error) bool {
|
||||
switch cfg.invokeFlag {
|
||||
case "debug-shell":
|
||||
return true
|
||||
case "on-error":
|
||||
return retErr != nil
|
||||
default:
|
||||
return cfg.invokeFlag != ""
|
||||
}
|
||||
}
|
||||
|
||||
func parseInvokeConfig(invoke string) (cfg invokeConfig, err error) {
|
||||
cfg.invokeFlag = invoke
|
||||
cfg.Tty = true
|
||||
switch invoke {
|
||||
case "default", "debug-shell":
|
||||
return cfg, nil
|
||||
case "on-error":
|
||||
// NOTE: we overwrite the command to run because the original one should fail on the failed step.
|
||||
// TODO: make this configurable via flags or restorable from LLB.
|
||||
// Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113295900
|
||||
cfg.Cmd = []string{"/bin/sh"}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
csvReader := csv.NewReader(strings.NewReader(invoke))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return cfg, err
|
||||
}
|
||||
if len(fields) == 1 && !strings.Contains(fields[0], "=") {
|
||||
cfg.Cmd = []string{fields[0]}
|
||||
return cfg, nil
|
||||
}
|
||||
cfg.NoUser = true
|
||||
cfg.NoCwd = true
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return cfg, errors.Errorf("invalid value %s", field)
|
||||
}
|
||||
key := strings.ToLower(parts[0])
|
||||
value := parts[1]
|
||||
switch key {
|
||||
case "args":
|
||||
cfg.Cmd = append(cfg.Cmd, value) // TODO: support JSON
|
||||
case "entrypoint":
|
||||
cfg.Entrypoint = append(cfg.Entrypoint, value) // TODO: support JSON
|
||||
case "env":
|
||||
cfg.Env = append(cfg.Env, value)
|
||||
case "user":
|
||||
cfg.User = value
|
||||
cfg.NoUser = false
|
||||
case "cwd":
|
||||
cfg.Cwd = value
|
||||
cfg.NoCwd = false
|
||||
case "tty":
|
||||
cfg.Tty, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return cfg, errors.Errorf("failed to parse tty: %v", err)
|
||||
}
|
||||
default:
|
||||
return cfg, errors.Errorf("unknown key %q", key)
|
||||
}
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func listToMap(values []string, defaultEnv bool) map[string]string {
|
||||
func listToMap(values []string, defaultEnv bool) (map[string]string, error) {
|
||||
result := make(map[string]string, len(values))
|
||||
for _, value := range values {
|
||||
kv := strings.SplitN(value, "=", 2)
|
||||
if len(kv) == 1 {
|
||||
if defaultEnv {
|
||||
v, ok := os.LookupEnv(kv[0])
|
||||
if ok {
|
||||
result[kv[0]] = v
|
||||
}
|
||||
} else {
|
||||
result[kv[0]] = ""
|
||||
k, v, hasValue := strings.Cut(value, "=")
|
||||
if k == "" {
|
||||
return nil, errors.Errorf("invalid key-value pair %q: empty key", value)
|
||||
}
|
||||
if hasValue {
|
||||
result[k] = v
|
||||
} else if defaultEnv {
|
||||
if envVal, ok := os.LookupEnv(k); ok {
|
||||
result[k] = envVal
|
||||
}
|
||||
} else {
|
||||
result[kv[0]] = kv[1]
|
||||
result[k] = ""
|
||||
}
|
||||
}
|
||||
return result
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func dockerUlimitToControllerUlimit(u *dockeropts.UlimitOpt) *controllerapi.UlimitOpt {
|
||||
@@ -781,8 +815,8 @@ func dockerUlimitToControllerUlimit(u *dockeropts.UlimitOpt) *controllerapi.Ulim
|
||||
return &controllerapi.UlimitOpt{Values: values}
|
||||
}
|
||||
|
||||
func printWarnings(w io.Writer, warnings []client.VertexWarning, mode string) {
|
||||
if len(warnings) == 0 || mode == progress.PrinterModeQuiet {
|
||||
func printWarnings(w io.Writer, warnings []client.VertexWarning, mode progressui.DisplayMode) {
|
||||
if len(warnings) == 0 || mode == progressui.QuietMode || mode == progressui.RawJSONMode {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "\n ")
|
||||
@@ -829,8 +863,12 @@ func printResult(f *controllerapi.PrintFunc, res map[string]string) error {
|
||||
return printValue(targets.PrintTargets, targets.SubrequestsTargetsDefinition.Version, f.Format, res)
|
||||
case "subrequests.describe":
|
||||
return printValue(subrequests.PrintDescribe, subrequests.SubrequestsDescribeDefinition.Version, f.Format, res)
|
||||
case "lint":
|
||||
return printValue(lint.PrintLintViolations, lint.SubrequestLintDefinition.Version, f.Format, res)
|
||||
default:
|
||||
if dt, ok := res["result.txt"]; ok {
|
||||
if dt, ok := res["result.json"]; ok && f.Format == "json" {
|
||||
fmt.Println(dt)
|
||||
} else if dt, ok := res["result.txt"]; ok {
|
||||
fmt.Print(dt)
|
||||
} else {
|
||||
log.Printf("%s %+v", f, res)
|
||||
@@ -854,3 +892,143 @@ func printValue(printer printFunc, version string, format string, res map[string
|
||||
}
|
||||
return printer([]byte(res["result.json"]), os.Stdout)
|
||||
}
|
||||
|
||||
type invokeConfig struct {
|
||||
controllerapi.InvokeConfig
|
||||
onFlag string
|
||||
invokeFlag string
|
||||
}
|
||||
|
||||
func (cfg *invokeConfig) needsDebug(retErr error) bool {
|
||||
switch cfg.onFlag {
|
||||
case "always":
|
||||
return true
|
||||
case "error":
|
||||
return retErr != nil
|
||||
default:
|
||||
return cfg.invokeFlag != ""
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *invokeConfig) runDebug(ctx context.Context, ref string, options *controllerapi.BuildOptions, c control.BuildxController, stdin io.ReadCloser, stdout io.WriteCloser, stderr console.File, progress *progress.Printer) (*monitor.MonitorBuildResult, error) {
|
||||
con := console.Current()
|
||||
if err := con.SetRaw(); err != nil {
|
||||
// TODO: run disconnect in build command (on error case)
|
||||
if err := c.Disconnect(ctx, ref); err != nil {
|
||||
logrus.Warnf("disconnect error: %v", err)
|
||||
}
|
||||
return nil, errors.Errorf("failed to configure terminal: %v", err)
|
||||
}
|
||||
defer con.Reset()
|
||||
return monitor.RunMonitor(ctx, ref, options, cfg.InvokeConfig, c, stdin, stdout, stderr, progress)
|
||||
}
|
||||
|
||||
func (cfg *invokeConfig) parseInvokeConfig(invoke, on string) error {
|
||||
cfg.onFlag = on
|
||||
cfg.invokeFlag = invoke
|
||||
cfg.Tty = true
|
||||
cfg.NoCmd = true
|
||||
switch invoke {
|
||||
case "default", "":
|
||||
return nil
|
||||
case "on-error":
|
||||
// NOTE: we overwrite the command to run because the original one should fail on the failed step.
|
||||
// TODO: make this configurable via flags or restorable from LLB.
|
||||
// Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113295900
|
||||
cfg.Cmd = []string{"/bin/sh"}
|
||||
cfg.NoCmd = false
|
||||
return nil
|
||||
}
|
||||
|
||||
csvReader := csv.NewReader(strings.NewReader(invoke))
|
||||
csvReader.LazyQuotes = true
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(fields) == 1 && !strings.Contains(fields[0], "=") {
|
||||
cfg.Cmd = []string{fields[0]}
|
||||
cfg.NoCmd = false
|
||||
return nil
|
||||
}
|
||||
cfg.NoUser = true
|
||||
cfg.NoCwd = true
|
||||
for _, field := range fields {
|
||||
parts := strings.SplitN(field, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
return errors.Errorf("invalid value %s", field)
|
||||
}
|
||||
key := strings.ToLower(parts[0])
|
||||
value := parts[1]
|
||||
switch key {
|
||||
case "args":
|
||||
cfg.Cmd = append(cfg.Cmd, maybeJSONArray(value)...)
|
||||
cfg.NoCmd = false
|
||||
case "entrypoint":
|
||||
cfg.Entrypoint = append(cfg.Entrypoint, maybeJSONArray(value)...)
|
||||
if cfg.Cmd == nil {
|
||||
cfg.Cmd = []string{}
|
||||
cfg.NoCmd = false
|
||||
}
|
||||
case "env":
|
||||
cfg.Env = append(cfg.Env, maybeJSONArray(value)...)
|
||||
case "user":
|
||||
cfg.User = value
|
||||
cfg.NoUser = false
|
||||
case "cwd":
|
||||
cfg.Cwd = value
|
||||
cfg.NoCwd = false
|
||||
case "tty":
|
||||
cfg.Tty, err = strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
return errors.Errorf("failed to parse tty: %v", err)
|
||||
}
|
||||
default:
|
||||
return errors.Errorf("unknown key %q", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func maybeJSONArray(v string) []string {
|
||||
var list []string
|
||||
if err := json.Unmarshal([]byte(v), &list); err == nil {
|
||||
return list
|
||||
}
|
||||
return []string{v}
|
||||
}
|
||||
|
||||
// timeBuildCommand will start a timer for timing the build command. It records the time when the returned
|
||||
// function is invoked into a metric.
|
||||
func timeBuildCommand(mp metric.MeterProvider, attrs attribute.Set) func(err error) {
|
||||
meter := metricutil.Meter(mp)
|
||||
counter, _ := meter.Float64Counter("command.time",
|
||||
metric.WithDescription("Measures the duration of the build command."),
|
||||
metric.WithUnit("ms"),
|
||||
)
|
||||
|
||||
start := time.Now()
|
||||
return func(err error) {
|
||||
dur := float64(time.Since(start)) / float64(time.Millisecond)
|
||||
extraAttrs := attribute.NewSet()
|
||||
if err != nil {
|
||||
extraAttrs = attribute.NewSet(
|
||||
attribute.String("error.type", otelErrorType(err)),
|
||||
)
|
||||
}
|
||||
counter.Add(context.Background(), dur,
|
||||
metric.WithAttributeSet(attrs),
|
||||
metric.WithAttributeSet(extraAttrs),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// otelErrorType returns an attribute for the error type based on the error category.
|
||||
// If nil, this function returns an invalid attribute.
|
||||
func otelErrorType(err error) string {
|
||||
name := "generic"
|
||||
if errors.Is(err, context.Canceled) {
|
||||
name = "canceled"
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
@@ -3,302 +3,72 @@ package commands
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/driver"
|
||||
k8sutil "github.com/docker/buildx/driver/kubernetes/util"
|
||||
remoteutil "github.com/docker/buildx/driver/remote/util"
|
||||
"github.com/docker/buildx/localstate"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/cobrautil"
|
||||
"github.com/docker/buildx/util/cobrautil/completion"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/dockerutil"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
dopts "github.com/docker/cli/opts"
|
||||
"github.com/google/shlex"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type createOptions struct {
|
||||
name string
|
||||
driver string
|
||||
nodeName string
|
||||
platform []string
|
||||
actionAppend bool
|
||||
actionLeave bool
|
||||
use bool
|
||||
flags string
|
||||
configFile string
|
||||
driverOpts []string
|
||||
bootstrap bool
|
||||
name string
|
||||
driver string
|
||||
nodeName string
|
||||
platform []string
|
||||
actionAppend bool
|
||||
actionLeave bool
|
||||
use bool
|
||||
driverOpts []string
|
||||
buildkitdFlags string
|
||||
buildkitdConfigFile string
|
||||
bootstrap bool
|
||||
// upgrade bool // perform upgrade of the driver
|
||||
}
|
||||
|
||||
func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
if in.name == "default" {
|
||||
return errors.Errorf("default is a reserved name and cannot be used to identify builder instance")
|
||||
}
|
||||
|
||||
if in.actionLeave {
|
||||
if in.name == "" {
|
||||
return errors.Errorf("leave requires instance name")
|
||||
}
|
||||
if in.nodeName == "" {
|
||||
return errors.Errorf("leave requires node name but --node not set")
|
||||
}
|
||||
}
|
||||
|
||||
if in.actionAppend {
|
||||
if in.name == "" {
|
||||
logrus.Warnf("append used without name, creating a new instance instead")
|
||||
}
|
||||
}
|
||||
|
||||
func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, args []string) error {
|
||||
txn, release, err := storeutil.GetStore(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Ensure the file lock gets released no matter what happens.
|
||||
defer release()
|
||||
|
||||
name := in.name
|
||||
if name == "" {
|
||||
name, err = store.GenerateName(txn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !in.actionLeave && !in.actionAppend {
|
||||
contexts, err := dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range contexts {
|
||||
if c.Name == name {
|
||||
logrus.Warnf("instance name %q already exists as context builder", name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ng, err := txn.NodeGroupByName(name)
|
||||
if err != nil {
|
||||
if os.IsNotExist(errors.Cause(err)) {
|
||||
if in.actionAppend && in.name != "" {
|
||||
logrus.Warnf("failed to find %q for append, creating a new instance instead", in.name)
|
||||
}
|
||||
if in.actionLeave {
|
||||
return errors.Errorf("failed to find instance %q for leave", in.name)
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
buildkitHost := os.Getenv("BUILDKIT_HOST")
|
||||
|
||||
driverName := in.driver
|
||||
if driverName == "" {
|
||||
if ng != nil {
|
||||
driverName = ng.Driver
|
||||
} else if len(args) == 0 && buildkitHost != "" {
|
||||
driverName = "remote"
|
||||
} else {
|
||||
var arg string
|
||||
if len(args) > 0 {
|
||||
arg = args[0]
|
||||
}
|
||||
f, err := driver.GetDefaultFactory(ctx, arg, dockerCli.Client(), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f == nil {
|
||||
return errors.Errorf("no valid drivers found")
|
||||
}
|
||||
driverName = f.Name()
|
||||
}
|
||||
}
|
||||
|
||||
if ng != nil {
|
||||
if in.nodeName == "" && !in.actionAppend {
|
||||
return errors.Errorf("existing instance for %q but no append mode, specify --node to make changes for existing instances", name)
|
||||
}
|
||||
if driverName != ng.Driver {
|
||||
return errors.Errorf("existing instance for %q but has mismatched driver %q", name, ng.Driver)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := driver.GetFactory(driverName, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ngOriginal := ng
|
||||
if ngOriginal != nil {
|
||||
ngOriginal = ngOriginal.Copy()
|
||||
}
|
||||
|
||||
if ng == nil {
|
||||
ng = &store.NodeGroup{
|
||||
Name: name,
|
||||
Driver: driverName,
|
||||
}
|
||||
}
|
||||
|
||||
var flags []string
|
||||
if in.flags != "" {
|
||||
flags, err = shlex.Split(in.flags)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to parse buildkit flags")
|
||||
}
|
||||
if in.actionLeave {
|
||||
return builder.Leave(ctx, txn, dockerCli, builder.LeaveOpts{
|
||||
Name: in.name,
|
||||
NodeName: in.nodeName,
|
||||
})
|
||||
}
|
||||
|
||||
var ep string
|
||||
var setEp bool
|
||||
if in.actionLeave {
|
||||
if err := ng.Leave(in.nodeName); err != nil {
|
||||
return err
|
||||
}
|
||||
ls, err := localstate.New(confutil.ConfigDir(dockerCli))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ls.RemoveBuilderNode(ng.Name, in.nodeName); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
switch {
|
||||
case driverName == "kubernetes":
|
||||
if len(args) > 0 {
|
||||
logrus.Warnf("kubernetes driver does not support endpoint args %q", args[0])
|
||||
}
|
||||
// generate node name if not provided to avoid duplicated endpoint
|
||||
// error: https://github.com/docker/setup-buildx-action/issues/215
|
||||
nodeName := in.nodeName
|
||||
if nodeName == "" {
|
||||
nodeName, err = k8sutil.GenerateNodeName(name, txn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// naming endpoint to make --append works
|
||||
ep = (&url.URL{
|
||||
Scheme: driverName,
|
||||
Path: "/" + name,
|
||||
RawQuery: (&url.Values{
|
||||
"deployment": {nodeName},
|
||||
"kubeconfig": {os.Getenv("KUBECONFIG")},
|
||||
}).Encode(),
|
||||
}).String()
|
||||
setEp = false
|
||||
case driverName == "remote":
|
||||
if len(args) > 0 {
|
||||
ep = args[0]
|
||||
} else if buildkitHost != "" {
|
||||
ep = buildkitHost
|
||||
} else {
|
||||
return errors.Errorf("no remote endpoint provided")
|
||||
}
|
||||
ep, err = validateBuildkitEndpoint(ep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setEp = true
|
||||
case len(args) > 0:
|
||||
ep, err = validateEndpoint(dockerCli, args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setEp = true
|
||||
default:
|
||||
if dockerCli.CurrentContext() == "default" && dockerCli.DockerEndpoint().TLSData != nil {
|
||||
return errors.Errorf("could not create a builder instance with TLS data loaded from environment. Please use `docker context create <context-name>` to create a context for current environment and then create a builder instance with `docker buildx create <context-name>`")
|
||||
}
|
||||
ep, err = dockerutil.GetCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setEp = false
|
||||
}
|
||||
|
||||
m, err := csvToMap(in.driverOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if in.configFile == "" {
|
||||
// if buildkit config is not provided, check if the default one is
|
||||
// available and use it
|
||||
if f, ok := confutil.DefaultConfigFile(dockerCli); ok {
|
||||
logrus.Warnf("Using default BuildKit config in %s", f)
|
||||
in.configFile = f
|
||||
}
|
||||
}
|
||||
|
||||
if err := ng.Update(in.nodeName, ep, in.platform, setEp, in.actionAppend, flags, in.configFile, m); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) > 0 {
|
||||
ep = args[0]
|
||||
}
|
||||
|
||||
if err := txn.Save(ng); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(ng.Name),
|
||||
builder.WithStore(txn),
|
||||
builder.WithSkippedValidation(),
|
||||
)
|
||||
b, err := builder.Create(ctx, txn, dockerCli, builder.CreateOpts{
|
||||
Name: in.name,
|
||||
Driver: in.driver,
|
||||
NodeName: in.nodeName,
|
||||
Platforms: in.platform,
|
||||
DriverOpts: in.driverOpts,
|
||||
BuildkitdFlags: in.buildkitdFlags,
|
||||
BuildkitdConfigFile: in.buildkitdConfigFile,
|
||||
Use: in.use,
|
||||
Endpoint: ep,
|
||||
Append: in.actionAppend,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
nodes, err := b.LoadNodes(timeoutCtx, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, node := range nodes {
|
||||
if err := node.Err; err != nil {
|
||||
err := errors.Errorf("failed to initialize builder %s (%s): %s", ng.Name, node.Name, err)
|
||||
var err2 error
|
||||
if ngOriginal == nil {
|
||||
err2 = txn.Remove(ng.Name)
|
||||
} else {
|
||||
err2 = txn.Save(ngOriginal)
|
||||
}
|
||||
if err2 != nil {
|
||||
logrus.Warnf("Could not rollback to previous state: %s", err2)
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if in.use && ep != "" {
|
||||
current, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := txn.SetCurrent(current, ng.Name, false, false); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// The store is no longer used from this point.
|
||||
// Release it so we aren't holding the file lock during the boot.
|
||||
release()
|
||||
|
||||
if in.bootstrap {
|
||||
if _, err = b.Boot(ctx); err != nil {
|
||||
@@ -306,7 +76,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", ng.Name)
|
||||
fmt.Printf("%s\n", b.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -326,7 +96,7 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
||||
Short: "Create a new builder instance",
|
||||
Args: cli.RequiresMaxArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runCreate(dockerCli, options, args)
|
||||
return runCreate(cmd.Context(), dockerCli, options, args)
|
||||
},
|
||||
ValidArgsFunction: completion.Disable,
|
||||
}
|
||||
@@ -336,12 +106,16 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
||||
flags.StringVar(&options.name, "name", "", "Builder instance name")
|
||||
flags.StringVar(&options.driver, "driver", "", fmt.Sprintf("Driver to use (available: %s)", drivers.String()))
|
||||
flags.StringVar(&options.nodeName, "node", "", "Create/modify node with given name")
|
||||
flags.StringVar(&options.flags, "buildkitd-flags", "", "Flags for buildkitd daemon")
|
||||
flags.StringVar(&options.configFile, "config", "", "BuildKit config file")
|
||||
flags.StringArrayVar(&options.platform, "platform", []string{}, "Fixed platforms for current node")
|
||||
flags.StringArrayVar(&options.driverOpts, "driver-opt", []string{}, "Options for the driver")
|
||||
flags.BoolVar(&options.bootstrap, "bootstrap", false, "Boot builder after creation")
|
||||
flags.StringVar(&options.buildkitdFlags, "buildkitd-flags", "", "BuildKit daemon flags")
|
||||
|
||||
// we allow for both "--config" and "--buildkitd-config", although the latter is the recommended way to avoid ambiguity.
|
||||
flags.StringVar(&options.buildkitdConfigFile, "buildkitd-config", "", "BuildKit daemon config file")
|
||||
flags.StringVar(&options.buildkitdConfigFile, "config", "", "BuildKit daemon config file")
|
||||
flags.MarkHidden("config")
|
||||
|
||||
flags.BoolVar(&options.bootstrap, "bootstrap", false, "Boot builder after creation")
|
||||
flags.BoolVar(&options.actionAppend, "append", false, "Append a node to builder instead of changing it")
|
||||
flags.BoolVar(&options.actionLeave, "leave", false, "Remove a node from builder instead of changing it")
|
||||
flags.BoolVar(&options.use, "use", false, "Set the current builder instance")
|
||||
@@ -351,49 +125,3 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func csvToMap(in []string) (map[string]string, error) {
|
||||
if len(in) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
m := make(map[string]string, len(in))
|
||||
for _, s := range in {
|
||||
csvReader := csv.NewReader(strings.NewReader(s))
|
||||
fields, err := csvReader.Read()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, v := range fields {
|
||||
p := strings.SplitN(v, "=", 2)
|
||||
if len(p) != 2 {
|
||||
return nil, errors.Errorf("invalid value %q, expecting k=v", v)
|
||||
}
|
||||
m[p[0]] = p[1]
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// validateEndpoint validates that endpoint is either a context or a docker host
|
||||
func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
|
||||
dem, err := dockerutil.GetDockerEndpoint(dockerCli, ep)
|
||||
if err == nil && dem != nil {
|
||||
if ep == "default" {
|
||||
return dem.Host, nil
|
||||
}
|
||||
return ep, nil
|
||||
}
|
||||
h, err := dopts.ParseHost(true, ep)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "failed to parse endpoint %s", ep)
|
||||
}
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// validateBuildkitEndpoint validates that endpoint is a valid buildkit host
|
||||
func validateBuildkitEndpoint(ep string) (string, error) {
|
||||
if err := remoteutil.IsValidEndpoint(ep); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return ep, nil
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCsvToMap(t *testing.T) {
|
||||
d := []string{
|
||||
"\"tolerations=key=foo,value=bar;key=foo2,value=bar2\",replicas=1",
|
||||
"namespace=default",
|
||||
}
|
||||
r, err := csvToMap(d)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Contains(t, r, "tolerations")
|
||||
require.Equal(t, r["tolerations"], "key=foo,value=bar;key=foo2,value=bar2")
|
||||
|
||||
require.Contains(t, r, "replicas")
|
||||
require.Equal(t, r["replicas"], "1")
|
||||
|
||||
require.Contains(t, r, "namespace")
|
||||
require.Equal(t, r["namespace"], "default")
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/docker/buildx/controller"
|
||||
"github.com/docker/buildx/controller/control"
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
"github.com/docker/buildx/monitor"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func debugShellCmd(dockerCli command.Cli) *cobra.Command {
|
||||
var options control.ControlOptions
|
||||
var progressMode string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "debug-shell",
|
||||
Short: "Start a monitor",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, os.Stderr, progressMode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
c, err := controller.NewController(ctx, options, dockerCli, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := c.Close(); err != nil {
|
||||
logrus.Warnf("failed to close server connection %v", err)
|
||||
}
|
||||
}()
|
||||
con := console.Current()
|
||||
if err := con.SetRaw(); err != nil {
|
||||
return errors.Errorf("failed to configure terminal: %v", err)
|
||||
}
|
||||
|
||||
err = monitor.RunMonitor(ctx, "", nil, controllerapi.InvokeConfig{
|
||||
Tty: true,
|
||||
}, c, os.Stdin, os.Stdout, os.Stderr, printer)
|
||||
con.Reset()
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.StringVar(&options.Root, "root", "", "Specify root directory of server to connect [experimental]")
|
||||
flags.BoolVar(&options.Detach, "detach", runtime.GOOS == "linux", "Detach buildx server (supported only on linux) [experimental]")
|
||||
flags.StringVar(&options.ServerConfig, "server-config", "", "Specify buildx server config file (used only when launching new server) [experimental]")
|
||||
flags.StringVar(&progressMode, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func addDebugShellCommand(cmd *cobra.Command, dockerCli command.Cli) {
|
||||
cmd.AddCommand(
|
||||
debugShellCmd(dockerCli),
|
||||
)
|
||||
}
|
||||
92
commands/debug/root.go
Normal file
92
commands/debug/root.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package debug
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"github.com/docker/buildx/controller"
|
||||
"github.com/docker/buildx/controller/control"
|
||||
controllerapi "github.com/docker/buildx/controller/pb"
|
||||
"github.com/docker/buildx/monitor"
|
||||
"github.com/docker/buildx/util/cobrautil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// DebugConfig is a user-specified configuration for the debugger.
|
||||
type DebugConfig struct {
|
||||
// InvokeFlag is a flag to configure the launched debugger and the commaned executed on the debugger.
|
||||
InvokeFlag string
|
||||
|
||||
// OnFlag is a flag to configure the timing of launching the debugger.
|
||||
OnFlag string
|
||||
}
|
||||
|
||||
// DebuggableCmd is a command that supports debugger with recognizing the user-specified DebugConfig.
|
||||
type DebuggableCmd interface {
|
||||
// NewDebugger returns the new *cobra.Command with support for the debugger with recognizing DebugConfig.
|
||||
NewDebugger(*DebugConfig) *cobra.Command
|
||||
}
|
||||
|
||||
func RootCmd(dockerCli command.Cli, children ...DebuggableCmd) *cobra.Command {
|
||||
var controlOptions control.ControlOptions
|
||||
var progressMode string
|
||||
var options DebugConfig
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "debug",
|
||||
Short: "Start debugger",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
printer, err := progress.NewPrinter(context.TODO(), os.Stderr, progressui.DisplayMode(progressMode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.TODO()
|
||||
c, err := controller.NewController(ctx, controlOptions, dockerCli, printer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := c.Close(); err != nil {
|
||||
logrus.Warnf("failed to close server connection %v", err)
|
||||
}
|
||||
}()
|
||||
con := console.Current()
|
||||
if err := con.SetRaw(); err != nil {
|
||||
return errors.Errorf("failed to configure terminal: %v", err)
|
||||
}
|
||||
|
||||
_, err = monitor.RunMonitor(ctx, "", nil, controllerapi.InvokeConfig{
|
||||
Tty: true,
|
||||
}, c, dockerCli.In(), os.Stdout, os.Stderr, printer)
|
||||
con.Reset()
|
||||
return err
|
||||
},
|
||||
}
|
||||
cobrautil.MarkCommandExperimental(cmd)
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&options.InvokeFlag, "invoke", "", "Launch a monitor with executing specified command")
|
||||
flags.StringVar(&options.OnFlag, "on", "error", "When to launch the monitor ([always, error])")
|
||||
|
||||
flags.StringVar(&controlOptions.Root, "root", "", "Specify root directory of server to connect for the monitor")
|
||||
flags.BoolVar(&controlOptions.Detach, "detach", runtime.GOOS == "linux", "Detach buildx server for the monitor (supported only on linux)")
|
||||
flags.StringVar(&controlOptions.ServerConfig, "server-config", "", "Specify buildx server config file for the monitor (used only when launching new server)")
|
||||
flags.StringVar(&progressMode, "progress", "auto", `Set type of progress output ("auto", "plain", "tty") for the monitor. Use plain to show container output`)
|
||||
|
||||
cobrautil.MarkFlagsExperimental(flags, "invoke", "on", "root", "detach", "server-config")
|
||||
|
||||
for _, c := range children {
|
||||
cmd.AddCommand(c.NewDebugger(&options))
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
132
commands/dial_stdio.go
Normal file
132
commands/dial_stdio.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/docker/buildx/build"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type stdioOptions struct {
|
||||
builder string
|
||||
platform string
|
||||
progress string
|
||||
}
|
||||
|
||||
func runDialStdio(dockerCli command.Cli, opts stdioOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
contextPathHash, _ := os.Getwd()
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(opts.builder),
|
||||
builder.WithContextPathHash(contextPathHash),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
|
||||
return errors.Wrapf(err, "failed to update builder last activity time")
|
||||
}
|
||||
nodes, err := b.LoadNodes(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printer, err := progress.NewPrinter(ctx, os.Stderr, progressui.DisplayMode(opts.progress), progress.WithPhase("dial-stdio"), progress.WithDesc("builder: "+b.Name, "builder:"+b.Name))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var p *v1.Platform
|
||||
if opts.platform != "" {
|
||||
pp, err := platforms.Parse(opts.platform)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "invalid platform %q", opts.platform)
|
||||
}
|
||||
p = &pp
|
||||
}
|
||||
|
||||
defer printer.Wait()
|
||||
|
||||
return progress.Wrap("Proxying to builder", printer.Write, func(sub progress.SubLogger) error {
|
||||
var conn net.Conn
|
||||
|
||||
err := sub.Wrap("Dialing builder", func() error {
|
||||
conn, err = build.Dial(ctx, nodes, printer, p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
closeWrite(conn)
|
||||
}()
|
||||
|
||||
var eg errgroup.Group
|
||||
|
||||
eg.Go(func() error {
|
||||
_, err := io.Copy(conn, os.Stdin)
|
||||
closeWrite(conn)
|
||||
return err
|
||||
})
|
||||
eg.Go(func() error {
|
||||
_, err := io.Copy(os.Stdout, conn)
|
||||
closeRead(conn)
|
||||
return err
|
||||
})
|
||||
return eg.Wait()
|
||||
})
|
||||
}
|
||||
|
||||
func closeRead(conn net.Conn) error {
|
||||
if c, ok := conn.(interface{ CloseRead() error }); ok {
|
||||
return c.CloseRead()
|
||||
}
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
func closeWrite(conn net.Conn) error {
|
||||
if c, ok := conn.(interface{ CloseWrite() error }); ok {
|
||||
return c.CloseWrite()
|
||||
}
|
||||
return conn.Close()
|
||||
}
|
||||
|
||||
func dialStdioCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
opts := stdioOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "dial-stdio",
|
||||
Short: "Proxy current stdio streams to builder instance",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.builder = rootOpts.builder
|
||||
return runDialStdio(dockerCli, opts)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
cmd.Flags()
|
||||
flags.StringVar(&opts.platform, "platform", os.Getenv("DOCKER_DEFAULT_PLATFORM"), "Target platform: this is used for node selection")
|
||||
flags.StringVar(&opts.progress, "progress", "quiet", "Set type of progress output (auto, plain, tty).")
|
||||
return cmd
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
@@ -26,9 +26,7 @@ type duOptions struct {
|
||||
verbose bool
|
||||
}
|
||||
|
||||
func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
func runDiskUsage(ctx context.Context, dockerCli command.Cli, opts duOptions) error {
|
||||
pi, err := toBuildkitPruneInfo(opts.filter.Value())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -39,7 +37,7 @@ func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
nodes, err := b.LoadNodes(ctx, false)
|
||||
nodes, err := b.LoadNodes(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -114,7 +112,7 @@ func duCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.builder = rootOpts.builder
|
||||
return runDiskUsage(dockerCli, options)
|
||||
return runDiskUsage(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.Disable,
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/distribution/reference"
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/util/cobrautil/completion"
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/moby/buildkit/util/progress/progressui"
|
||||
"github.com/opencontainers/go-digest"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
@@ -25,12 +25,13 @@ type createOptions struct {
|
||||
builder string
|
||||
files []string
|
||||
tags []string
|
||||
annotations []string
|
||||
dryrun bool
|
||||
actionAppend bool
|
||||
progress string
|
||||
}
|
||||
|
||||
func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, args []string) error {
|
||||
if len(args) == 0 && len(in.files) == 0 {
|
||||
return errors.Errorf("no sources specified")
|
||||
}
|
||||
@@ -111,8 +112,6 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
ctx := appcontext.Context()
|
||||
|
||||
b, err := builder.New(dockerCli, builder.WithName(in.builder))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -154,7 +153,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
dt, desc, err := r.Combine(ctx, srcs)
|
||||
dt, desc, err := r.Combine(ctx, srcs, in.annotations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -169,7 +168,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
||||
|
||||
ctx2, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
printer, err := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, in.progress)
|
||||
printer, err := progress.NewPrinter(ctx2, os.Stderr, progressui.DisplayMode(in.progress))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -272,7 +271,7 @@ func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
||||
Short: "Create a new image based on source images",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.builder = *opts.Builder
|
||||
return runCreate(dockerCli, options, args)
|
||||
return runCreate(cmd.Context(), dockerCli, options, args)
|
||||
},
|
||||
ValidArgsFunction: completion.Disable,
|
||||
}
|
||||
@@ -283,6 +282,7 @@ func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
||||
flags.BoolVar(&options.dryrun, "dry-run", false, "Show final image instead of pushing")
|
||||
flags.BoolVar(&options.actionAppend, "append", false, "Append to existing manifest")
|
||||
flags.StringVar(&options.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`)
|
||||
flags.StringArrayVarP(&options.annotations, "annotation", "", []string{}, "Add annotation to the image")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/util/cobrautil/completion"
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/cli-docs-tool/annotation"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -18,9 +19,7 @@ type inspectOptions struct {
|
||||
raw bool
|
||||
}
|
||||
|
||||
func runInspect(dockerCli command.Cli, in inspectOptions, name string) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
func runInspect(ctx context.Context, dockerCli command.Cli, in inspectOptions, name string) error {
|
||||
if in.format != "" && in.raw {
|
||||
return errors.Errorf("format and raw cannot be used together")
|
||||
}
|
||||
@@ -51,7 +50,7 @@ func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||
Args: cli.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.builder = *rootOpts.Builder
|
||||
return runInspect(dockerCli, options, args[0])
|
||||
return runInspect(cmd.Context(), dockerCli, options, args[0])
|
||||
},
|
||||
ValidArgsFunction: completion.Disable,
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/debug"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -26,9 +25,7 @@ type inspectOptions struct {
|
||||
builder string
|
||||
}
|
||||
|
||||
func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
func runInspect(ctx context.Context, dockerCli command.Cli, in inspectOptions) error {
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(in.builder),
|
||||
builder.WithSkippedValidation(),
|
||||
@@ -40,7 +37,7 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
|
||||
nodes, err := b.LoadNodes(timeoutCtx, true)
|
||||
nodes, err := b.LoadNodes(timeoutCtx, builder.WithData())
|
||||
if in.bootstrap {
|
||||
var ok bool
|
||||
ok, err = b.Boot(ctx)
|
||||
@@ -48,7 +45,7 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
nodes, err = b.LoadNodes(timeoutCtx, true)
|
||||
nodes, err = b.LoadNodes(timeoutCtx, builder.WithData())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,13 +84,16 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||
} else {
|
||||
fmt.Fprintf(w, "Status:\t%s\n", nodes[i].DriverInfo.Status)
|
||||
if len(n.Flags) > 0 {
|
||||
fmt.Fprintf(w, "Flags:\t%s\n", strings.Join(n.Flags, " "))
|
||||
if len(n.BuildkitdFlags) > 0 {
|
||||
fmt.Fprintf(w, "BuildKit daemon flags:\t%s\n", strings.Join(n.BuildkitdFlags, " "))
|
||||
}
|
||||
if nodes[i].Version != "" {
|
||||
fmt.Fprintf(w, "Buildkit:\t%s\n", nodes[i].Version)
|
||||
fmt.Fprintf(w, "BuildKit version:\t%s\n", nodes[i].Version)
|
||||
}
|
||||
platforms := platformutil.FormatInGroups(n.Node.Platforms, n.Platforms)
|
||||
if len(platforms) > 0 {
|
||||
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platforms, ", "))
|
||||
}
|
||||
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.FormatInGroups(n.Node.Platforms, n.Platforms), ", "))
|
||||
if debug.IsEnabled() {
|
||||
fmt.Fprintf(w, "Features:\n")
|
||||
features := nodes[i].Driver.Features(ctx)
|
||||
@@ -147,7 +147,7 @@ func inspectCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
if len(args) > 0 {
|
||||
options.builder = args[0]
|
||||
}
|
||||
return runInspect(dockerCli, options)
|
||||
return runInspect(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
||||
}
|
||||
|
||||
235
commands/ls.go
235
commands/ls.go
@@ -2,30 +2,43 @@ package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/builder"
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/cobrautil"
|
||||
"github.com/docker/buildx/util/cobrautil/completion"
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
const (
|
||||
lsNameNodeHeader = "NAME/NODE"
|
||||
lsDriverEndpointHeader = "DRIVER/ENDPOINT"
|
||||
lsStatusHeader = "STATUS"
|
||||
lsLastActivityHeader = "LAST ACTIVITY"
|
||||
lsBuildkitHeader = "BUILDKIT"
|
||||
lsPlatformsHeader = "PLATFORMS"
|
||||
|
||||
lsIndent = ` \_ `
|
||||
|
||||
lsDefaultTableFormat = "table {{.Name}}\t{{.DriverEndpoint}}\t{{.Status}}\t{{.Buildkit}}\t{{.Platforms}}"
|
||||
)
|
||||
|
||||
type lsOptions struct {
|
||||
format string
|
||||
}
|
||||
|
||||
func runLs(dockerCli command.Cli, in lsOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
func runLs(ctx context.Context, dockerCli command.Cli, in lsOptions) error {
|
||||
txn, release, err := storeutil.GetStore(dockerCli)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -49,7 +62,7 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
||||
for _, b := range builders {
|
||||
func(b *builder.Builder) {
|
||||
eg.Go(func() error {
|
||||
_, _ = b.LoadNodes(timeoutCtx, true)
|
||||
_, _ = b.LoadNodes(timeoutCtx, builder.WithData())
|
||||
return nil
|
||||
})
|
||||
}(b)
|
||||
@@ -59,22 +72,9 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
|
||||
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tBUILDKIT\tPLATFORMS\n")
|
||||
|
||||
printErr := false
|
||||
for _, b := range builders {
|
||||
if current.Name == b.Name {
|
||||
b.Name += " *"
|
||||
}
|
||||
if ok := printBuilder(w, b); !ok {
|
||||
printErr = true
|
||||
}
|
||||
}
|
||||
|
||||
w.Flush()
|
||||
|
||||
if printErr {
|
||||
if hasErrors, err := lsPrint(dockerCli, current, builders, in.format); err != nil {
|
||||
return err
|
||||
} else if hasErrors {
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "\n")
|
||||
for _, b := range builders {
|
||||
if b.Err() != nil {
|
||||
@@ -92,31 +92,6 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func printBuilder(w io.Writer, b *builder.Builder) (ok bool) {
|
||||
ok = true
|
||||
var err string
|
||||
if b.Err() != nil {
|
||||
ok = false
|
||||
err = "error"
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t\t\n", b.Name, b.Driver, err)
|
||||
if b.Err() == nil {
|
||||
for _, n := range b.Nodes() {
|
||||
var status string
|
||||
if n.DriverInfo != nil {
|
||||
status = n.DriverInfo.Status.String()
|
||||
}
|
||||
if n.Err != nil {
|
||||
ok = false
|
||||
fmt.Fprintf(w, " %s\t%s\t%s\t\t\n", n.Name, n.Endpoint, "error")
|
||||
} else {
|
||||
fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, n.Version, strings.Join(platformutil.FormatInGroups(n.Node.Platforms, n.Platforms), ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func lsCmd(dockerCli command.Cli) *cobra.Command {
|
||||
var options lsOptions
|
||||
|
||||
@@ -125,13 +100,175 @@ func lsCmd(dockerCli command.Cli) *cobra.Command {
|
||||
Short: "List builder instances",
|
||||
Args: cli.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runLs(dockerCli, options)
|
||||
return runLs(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.Disable,
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&options.format, "format", formatter.TableFormatKey, "Format the output")
|
||||
|
||||
// hide builder persistent flag for this command
|
||||
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func lsPrint(dockerCli command.Cli, current *store.NodeGroup, builders []*builder.Builder, format string) (hasErrors bool, _ error) {
|
||||
if format == formatter.TableFormatKey {
|
||||
format = lsDefaultTableFormat
|
||||
}
|
||||
|
||||
ctx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.Format(format),
|
||||
}
|
||||
|
||||
sort.SliceStable(builders, func(i, j int) bool {
|
||||
ierr := builders[i].Err() != nil
|
||||
jerr := builders[j].Err() != nil
|
||||
if ierr && !jerr {
|
||||
return false
|
||||
} else if !ierr && jerr {
|
||||
return true
|
||||
}
|
||||
return i < j
|
||||
})
|
||||
|
||||
render := func(format func(subContext formatter.SubContext) error) error {
|
||||
for _, b := range builders {
|
||||
if err := format(&lsContext{
|
||||
Builder: &lsBuilder{
|
||||
Builder: b,
|
||||
Current: b.Name == current.Name,
|
||||
},
|
||||
format: ctx.Format,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if b.Err() != nil {
|
||||
if ctx.Format.IsTable() {
|
||||
hasErrors = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, n := range b.Nodes() {
|
||||
if n.Err != nil {
|
||||
if ctx.Format.IsTable() {
|
||||
hasErrors = true
|
||||
}
|
||||
}
|
||||
if err := format(&lsContext{
|
||||
format: ctx.Format,
|
||||
Builder: &lsBuilder{
|
||||
Builder: b,
|
||||
Current: b.Name == current.Name,
|
||||
},
|
||||
node: n,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
lsCtx := lsContext{}
|
||||
lsCtx.Header = formatter.SubHeaderContext{
|
||||
"Name": lsNameNodeHeader,
|
||||
"DriverEndpoint": lsDriverEndpointHeader,
|
||||
"LastActivity": lsLastActivityHeader,
|
||||
"Status": lsStatusHeader,
|
||||
"Buildkit": lsBuildkitHeader,
|
||||
"Platforms": lsPlatformsHeader,
|
||||
}
|
||||
|
||||
return hasErrors, ctx.Write(&lsCtx, render)
|
||||
}
|
||||
|
||||
type lsBuilder struct {
|
||||
*builder.Builder
|
||||
Current bool
|
||||
}
|
||||
|
||||
type lsContext struct {
|
||||
formatter.HeaderContext
|
||||
Builder *lsBuilder
|
||||
|
||||
format formatter.Format
|
||||
node builder.Node
|
||||
}
|
||||
|
||||
func (c *lsContext) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(c.Builder)
|
||||
}
|
||||
|
||||
func (c *lsContext) Name() string {
|
||||
if c.node.Name == "" {
|
||||
name := c.Builder.Name
|
||||
if c.Builder.Current && c.format.IsTable() {
|
||||
name += "*"
|
||||
}
|
||||
return name
|
||||
}
|
||||
if c.format.IsTable() {
|
||||
return lsIndent + c.node.Name
|
||||
}
|
||||
return c.node.Name
|
||||
}
|
||||
|
||||
func (c *lsContext) DriverEndpoint() string {
|
||||
if c.node.Name == "" {
|
||||
return c.Builder.Driver
|
||||
}
|
||||
if c.format.IsTable() {
|
||||
return lsIndent + c.node.Endpoint
|
||||
}
|
||||
return c.node.Endpoint
|
||||
}
|
||||
|
||||
func (c *lsContext) LastActivity() string {
|
||||
if c.node.Name != "" || c.Builder.LastActivity.IsZero() {
|
||||
return ""
|
||||
}
|
||||
return c.Builder.LastActivity.UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func (c *lsContext) Status() string {
|
||||
if c.node.Name == "" {
|
||||
if c.Builder.Err() != nil {
|
||||
return "error"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if c.node.Err != nil {
|
||||
return "error"
|
||||
}
|
||||
if c.node.DriverInfo != nil {
|
||||
return c.node.DriverInfo.Status.String()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c *lsContext) Buildkit() string {
|
||||
if c.node.Name == "" {
|
||||
return ""
|
||||
}
|
||||
return c.node.Version
|
||||
}
|
||||
|
||||
func (c *lsContext) Platforms() string {
|
||||
if c.node.Name == "" {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(platformutil.FormatInGroups(c.node.Node.Platforms, c.node.Platforms), ", ")
|
||||
}
|
||||
|
||||
func (c *lsContext) Error() string {
|
||||
if c.node.Name != "" && c.node.Err != nil {
|
||||
return c.node.Err.Error()
|
||||
} else if err := c.Builder.Err(); err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
@@ -15,7 +16,6 @@ import (
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -35,9 +35,7 @@ const (
|
||||
allCacheWarning = `WARNING! This will remove all build cache. Are you sure you want to continue?`
|
||||
)
|
||||
|
||||
func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
func runPrune(ctx context.Context, dockerCli command.Cli, opts pruneOptions) error {
|
||||
pruneFilters := opts.filter.Value()
|
||||
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
||||
|
||||
@@ -51,8 +49,12 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
||||
warning = allCacheWarning
|
||||
}
|
||||
|
||||
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return nil
|
||||
if !opts.force {
|
||||
if ok, err := prompt(ctx, dockerCli.In(), dockerCli.Out(), warning); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
|
||||
@@ -60,7 +62,7 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
||||
return err
|
||||
}
|
||||
|
||||
nodes, err := b.LoadNodes(ctx, false)
|
||||
nodes, err := b.LoadNodes(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -138,7 +140,7 @@ func pruneCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.builder = rootOpts.builder
|
||||
return runPrune(dockerCli, options)
|
||||
return runPrune(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.Disable,
|
||||
}
|
||||
|
||||
@@ -9,16 +9,14 @@ import (
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/store/storeutil"
|
||||
"github.com/docker/buildx/util/cobrautil/completion"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type rmOptions struct {
|
||||
builder string
|
||||
builders []string
|
||||
keepState bool
|
||||
keepDaemon bool
|
||||
allInactive bool
|
||||
@@ -29,11 +27,13 @@ const (
|
||||
rmInactiveWarning = `WARNING! This will remove all builders that are not in running state. Are you sure you want to continue?`
|
||||
)
|
||||
|
||||
func runRm(dockerCli command.Cli, in rmOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
if in.allInactive && !in.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), rmInactiveWarning) {
|
||||
return nil
|
||||
func runRm(ctx context.Context, dockerCli command.Cli, in rmOptions) error {
|
||||
if in.allInactive && !in.force {
|
||||
if ok, err := prompt(ctx, dockerCli.In(), dockerCli.Out(), rmInactiveWarning); err != nil {
|
||||
return err
|
||||
} else if !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
txn, release, err := storeutil.GetStore(dockerCli)
|
||||
@@ -46,33 +46,52 @@ func runRm(dockerCli command.Cli, in rmOptions) error {
|
||||
return rmAllInactive(ctx, txn, dockerCli, in)
|
||||
}
|
||||
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(in.builder),
|
||||
builder.WithStore(txn),
|
||||
builder.WithSkippedValidation(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
eg, _ := errgroup.WithContext(ctx)
|
||||
for _, name := range in.builders {
|
||||
func(name string) {
|
||||
eg.Go(func() (err error) {
|
||||
defer func() {
|
||||
if err == nil {
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", name)
|
||||
} else {
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "failed to remove %s: %v\n", name, err)
|
||||
}
|
||||
}()
|
||||
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(name),
|
||||
builder.WithStore(txn),
|
||||
builder.WithSkippedValidation(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
nodes, err := b.LoadNodes(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cb := b.ContextName(); cb != "" {
|
||||
return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb)
|
||||
}
|
||||
|
||||
err1 := rm(ctx, nodes, in)
|
||||
if err := txn.Remove(b.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}(name)
|
||||
}
|
||||
|
||||
nodes, err := b.LoadNodes(ctx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
if err := eg.Wait(); err != nil {
|
||||
return errors.New("failed to remove one or more builders")
|
||||
}
|
||||
|
||||
if cb := b.ContextName(); cb != "" {
|
||||
return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb)
|
||||
}
|
||||
|
||||
err1 := rm(ctx, nodes, in)
|
||||
if err := txn.Remove(b.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err1 != nil {
|
||||
return err1
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -80,25 +99,24 @@ func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
var options rmOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm [NAME]",
|
||||
Short: "Remove a builder instance",
|
||||
Args: cli.RequiresMaxArgs(1),
|
||||
Use: "rm [OPTIONS] [NAME] [NAME...]",
|
||||
Short: "Remove one or more builder instances",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
options.builder = rootOpts.builder
|
||||
options.builders = []string{rootOpts.builder}
|
||||
if len(args) > 0 {
|
||||
if options.allInactive {
|
||||
return errors.New("cannot specify builder name when --all-inactive is set")
|
||||
}
|
||||
options.builder = args[0]
|
||||
options.builders = args
|
||||
}
|
||||
return runRm(dockerCli, options)
|
||||
return runRm(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVar(&options.keepState, "keep-state", false, "Keep BuildKit state")
|
||||
flags.BoolVar(&options.keepDaemon, "keep-daemon", false, "Keep the buildkitd daemon running")
|
||||
flags.BoolVar(&options.keepDaemon, "keep-daemon", false, "Keep the BuildKit daemon running")
|
||||
flags.BoolVar(&options.allInactive, "all-inactive", false, "Remove all inactive builders")
|
||||
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||
|
||||
@@ -139,7 +157,7 @@ func rmAllInactive(ctx context.Context, txn *store.Txn, dockerCli command.Cli, i
|
||||
for _, b := range builders {
|
||||
func(b *builder.Builder) {
|
||||
eg.Go(func() error {
|
||||
nodes, err := b.LoadNodes(timeoutCtx, true)
|
||||
nodes, err := b.LoadNodes(timeoutCtx, builder.WithData())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot load %s", b.Name)
|
||||
}
|
||||
|
||||
@@ -3,14 +3,18 @@ package commands
|
||||
import (
|
||||
"os"
|
||||
|
||||
debugcmd "github.com/docker/buildx/commands/debug"
|
||||
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
||||
"github.com/docker/buildx/controller/remote"
|
||||
"github.com/docker/buildx/util/cobrautil/completion"
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/logutil"
|
||||
"github.com/docker/cli-docs-tool/annotation"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli-plugins/plugin"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/debug"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
@@ -27,12 +31,15 @@ func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Comman
|
||||
CompletionOptions: cobra.CompletionOptions{
|
||||
HiddenDefaultCmd: true,
|
||||
},
|
||||
}
|
||||
if isPlugin {
|
||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
cmd.SetContext(appcontext.Context())
|
||||
if !isPlugin {
|
||||
return nil
|
||||
}
|
||||
return plugin.PersistentPreRunE(cmd, args)
|
||||
}
|
||||
} else {
|
||||
},
|
||||
}
|
||||
if !isPlugin {
|
||||
// match plugin behavior for standalone mode
|
||||
// https://github.com/docker/cli/blob/6c9eb708fa6d17765d71965f90e1c59cea686ee9/cli-plugins/plugin/plugin.go#L117-L127
|
||||
cmd.SilenceUsage = true
|
||||
@@ -40,6 +47,11 @@ func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Comman
|
||||
cmd.TraverseChildren = true
|
||||
cmd.DisableFlagsInUseLine = true
|
||||
cli.DisableFlagsInUseLine(cmd)
|
||||
|
||||
// DEBUG=1 should perform the same as --debug at the docker root level
|
||||
if debug.IsEnabled() {
|
||||
debug.Enable()
|
||||
}
|
||||
}
|
||||
|
||||
logrus.SetFormatter(&logutil.Formatter{})
|
||||
@@ -52,16 +64,9 @@ func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Comman
|
||||
"using default config store",
|
||||
))
|
||||
|
||||
// filter out useless commandConn.CloseWrite warning message that can occur
|
||||
// when listing builder instances with "buildx ls" for those that are
|
||||
// unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
|
||||
// https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/connhelper/commandconn/commandconn.go#L203-L214
|
||||
logrus.AddHook(logutil.NewFilter([]logrus.Level{
|
||||
logrus.WarnLevel,
|
||||
},
|
||||
"commandConn.CloseWrite:",
|
||||
"commandConn.CloseRead:",
|
||||
))
|
||||
if !confutil.IsExperimental() {
|
||||
cmd.SetHelpTemplate(cmd.HelpTemplate() + "\nExperimental commands and flags are hidden. Set BUILDX_EXPERIMENTAL=1 to show them.\n")
|
||||
}
|
||||
|
||||
addCommands(cmd, dockerCli)
|
||||
return cmd
|
||||
@@ -76,9 +81,10 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
||||
rootFlags(opts, cmd.PersistentFlags())
|
||||
|
||||
cmd.AddCommand(
|
||||
buildCmd(dockerCli, opts),
|
||||
buildCmd(dockerCli, opts, nil),
|
||||
bakeCmd(dockerCli, opts),
|
||||
createCmd(dockerCli),
|
||||
dialStdioCmd(dockerCli, opts),
|
||||
rmCmd(dockerCli, opts),
|
||||
lsCmd(dockerCli),
|
||||
useCmd(dockerCli, opts),
|
||||
@@ -91,9 +97,11 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
||||
duCmd(dockerCli, opts),
|
||||
imagetoolscmd.RootCmd(dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
|
||||
)
|
||||
if isExperimental() {
|
||||
if confutil.IsExperimental() {
|
||||
cmd.AddCommand(debugcmd.RootCmd(dockerCli,
|
||||
newDebuggableBuild(dockerCli, opts),
|
||||
))
|
||||
remote.AddControllerCommands(cmd, dockerCli)
|
||||
addDebugShellCommand(cmd, dockerCli)
|
||||
}
|
||||
|
||||
cmd.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"github.com/docker/buildx/util/cobrautil/completion"
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/moby/buildkit/util/appcontext"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -15,9 +14,7 @@ type stopOptions struct {
|
||||
builder string
|
||||
}
|
||||
|
||||
func runStop(dockerCli command.Cli, in stopOptions) error {
|
||||
ctx := appcontext.Context()
|
||||
|
||||
func runStop(ctx context.Context, dockerCli command.Cli, in stopOptions) error {
|
||||
b, err := builder.New(dockerCli,
|
||||
builder.WithName(in.builder),
|
||||
builder.WithSkippedValidation(),
|
||||
@@ -25,7 +22,7 @@ func runStop(dockerCli command.Cli, in stopOptions) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
nodes, err := b.LoadNodes(ctx, false)
|
||||
nodes, err := b.LoadNodes(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -45,7 +42,7 @@ func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
if len(args) > 0 {
|
||||
options.builder = args[0]
|
||||
}
|
||||
return runStop(dockerCli, options)
|
||||
return runStop(cmd.Context(), dockerCli, options)
|
||||
},
|
||||
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
||||
}
|
||||
|
||||
@@ -35,10 +35,7 @@ func runUse(dockerCli command.Cli, in useOptions) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := txn.SetCurrent(ep, "", false, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return txn.SetCurrent(ep, "", false, false)
|
||||
}
|
||||
list, err := dockerCli.ContextStore().List()
|
||||
if err != nil {
|
||||
@@ -58,11 +55,7 @@ func runUse(dockerCli command.Cli, in useOptions) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := txn.SetCurrent(ep, in.builder, in.isGlobal, in.isDefault); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return txn.SetCurrent(ep, in.builder, in.isGlobal, in.isDefault)
|
||||
}
|
||||
|
||||
func useCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||
|
||||
57
commands/util.go
Normal file
57
commands/util.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli/streams"
|
||||
)
|
||||
|
||||
func prompt(ctx context.Context, ins io.Reader, out io.Writer, msg string) (bool, error) {
|
||||
done := make(chan struct{})
|
||||
var ok bool
|
||||
go func() {
|
||||
ok = promptForConfirmation(ins, out, msg)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false, context.Cause(ctx)
|
||||
case <-done:
|
||||
return ok, nil
|
||||
}
|
||||
}
|
||||
|
||||
// promptForConfirmation requests and checks confirmation from user.
|
||||
// This will display the provided message followed by ' [y/N] '. If
|
||||
// the user input 'y' or 'Y' it returns true other false. If no
|
||||
// message is provided "Are you sure you want to proceed? [y/N] "
|
||||
// will be used instead.
|
||||
//
|
||||
// Copied from github.com/docker/cli since the upstream version changed
|
||||
// recently with an incompatible change.
|
||||
//
|
||||
// See https://github.com/docker/buildx/pull/2359#discussion_r1544736494
|
||||
// for discussion on the issue.
|
||||
func promptForConfirmation(ins io.Reader, outs io.Writer, message string) bool {
|
||||
if message == "" {
|
||||
message = "Are you sure you want to proceed?"
|
||||
}
|
||||
message += " [y/N] "
|
||||
|
||||
_, _ = fmt.Fprint(outs, message)
|
||||
|
||||
// On Windows, force the use of the regular OS stdin stream.
|
||||
if runtime.GOOS == "windows" {
|
||||
ins = streams.NewIn(os.Stdin)
|
||||
}
|
||||
|
||||
reader := bufio.NewReader(ins)
|
||||
answer, _, _ := reader.ReadLine()
|
||||
return strings.ToLower(string(answer)) == "y"
|
||||
}
|
||||
@@ -53,17 +53,21 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
|
||||
InStream: inStream,
|
||||
NamedContexts: contexts,
|
||||
},
|
||||
BuildArgs: in.BuildArgs,
|
||||
ExtraHosts: in.ExtraHosts,
|
||||
Labels: in.Labels,
|
||||
NetworkMode: in.NetworkMode,
|
||||
NoCache: in.NoCache,
|
||||
NoCacheFilter: in.NoCacheFilter,
|
||||
Pull: in.Pull,
|
||||
ShmSize: dockeropts.MemBytes(in.ShmSize),
|
||||
Tags: in.Tags,
|
||||
Target: in.Target,
|
||||
Ulimits: controllerUlimitOpt2DockerUlimit(in.Ulimits),
|
||||
Ref: in.Ref,
|
||||
BuildArgs: in.BuildArgs,
|
||||
CgroupParent: in.CgroupParent,
|
||||
ExtraHosts: in.ExtraHosts,
|
||||
Labels: in.Labels,
|
||||
NetworkMode: in.NetworkMode,
|
||||
NoCache: in.NoCache,
|
||||
NoCacheFilter: in.NoCacheFilter,
|
||||
Pull: in.Pull,
|
||||
ShmSize: dockeropts.MemBytes(in.ShmSize),
|
||||
Tags: in.Tags,
|
||||
Target: in.Target,
|
||||
Ulimits: controllerUlimitOpt2DockerUlimit(in.Ulimits),
|
||||
GroupRef: in.GroupRef,
|
||||
WithProvenanceResponse: in.WithProvenanceResponse,
|
||||
}
|
||||
|
||||
platforms, err := platformutil.Parse(in.Platforms)
|
||||
@@ -73,7 +77,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
|
||||
opts.Platforms = platforms
|
||||
|
||||
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
||||
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(dockerConfig))
|
||||
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(dockerConfig, nil))
|
||||
|
||||
secrets, err := controllerapi.CreateSecrets(in.Secrets)
|
||||
if err != nil {
|
||||
@@ -96,39 +100,50 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
|
||||
return nil, nil, err
|
||||
}
|
||||
if in.ExportPush {
|
||||
if in.ExportLoad {
|
||||
return nil, nil, errors.Errorf("push and load may not be set together at the moment")
|
||||
var pushUsed bool
|
||||
for i := range outputs {
|
||||
if outputs[i].Type == client.ExporterImage {
|
||||
outputs[i].Attrs["push"] = "true"
|
||||
pushUsed = true
|
||||
}
|
||||
}
|
||||
if len(outputs) == 0 {
|
||||
outputs = []client.ExportEntry{{
|
||||
Type: "image",
|
||||
if !pushUsed {
|
||||
outputs = append(outputs, client.ExportEntry{
|
||||
Type: client.ExporterImage,
|
||||
Attrs: map[string]string{
|
||||
"push": "true",
|
||||
},
|
||||
}}
|
||||
} else {
|
||||
switch outputs[0].Type {
|
||||
case "image":
|
||||
outputs[0].Attrs["push"] = "true"
|
||||
default:
|
||||
return nil, nil, errors.Errorf("push and %q output can't be used together", outputs[0].Type)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
if in.ExportLoad {
|
||||
if len(outputs) == 0 {
|
||||
outputs = []client.ExportEntry{{
|
||||
Type: "docker",
|
||||
Attrs: map[string]string{},
|
||||
}}
|
||||
} else {
|
||||
switch outputs[0].Type {
|
||||
case "docker":
|
||||
default:
|
||||
return nil, nil, errors.Errorf("load and %q output can't be used together", outputs[0].Type)
|
||||
var loadUsed bool
|
||||
for i := range outputs {
|
||||
if outputs[i].Type == client.ExporterDocker {
|
||||
if _, ok := outputs[i].Attrs["dest"]; !ok {
|
||||
loadUsed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !loadUsed {
|
||||
outputs = append(outputs, client.ExportEntry{
|
||||
Type: client.ExporterDocker,
|
||||
Attrs: map[string]string{},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
annotations, err := buildflags.ParseAnnotations(in.Annotations)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, o := range outputs {
|
||||
for k, v := range annotations {
|
||||
o.Attrs[k.String()] = v
|
||||
}
|
||||
}
|
||||
|
||||
opts.Exports = outputs
|
||||
|
||||
opts.CacheFrom = controllerapi.CreateCaches(in.CacheFrom)
|
||||
@@ -168,7 +183,7 @@ func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.Build
|
||||
if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "failed to update builder last activity time")
|
||||
}
|
||||
nodes, err := b.LoadNodes(ctx, false)
|
||||
nodes, err := b.LoadNodes(ctx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@@ -271,37 +271,41 @@ func (m *BuildRequest) GetOptions() *BuildOptions {
|
||||
}
|
||||
|
||||
type BuildOptions struct {
|
||||
ContextPath string `protobuf:"bytes,1,opt,name=ContextPath,proto3" json:"ContextPath,omitempty"`
|
||||
DockerfileName string `protobuf:"bytes,2,opt,name=DockerfileName,proto3" json:"DockerfileName,omitempty"`
|
||||
PrintFunc *PrintFunc `protobuf:"bytes,3,opt,name=PrintFunc,proto3" json:"PrintFunc,omitempty"`
|
||||
NamedContexts map[string]string `protobuf:"bytes,4,rep,name=NamedContexts,proto3" json:"NamedContexts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
Allow []string `protobuf:"bytes,5,rep,name=Allow,proto3" json:"Allow,omitempty"`
|
||||
Attests []*Attest `protobuf:"bytes,6,rep,name=Attests,proto3" json:"Attests,omitempty"`
|
||||
BuildArgs map[string]string `protobuf:"bytes,7,rep,name=BuildArgs,proto3" json:"BuildArgs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
CacheFrom []*CacheOptionsEntry `protobuf:"bytes,8,rep,name=CacheFrom,proto3" json:"CacheFrom,omitempty"`
|
||||
CacheTo []*CacheOptionsEntry `protobuf:"bytes,9,rep,name=CacheTo,proto3" json:"CacheTo,omitempty"`
|
||||
CgroupParent string `protobuf:"bytes,10,opt,name=CgroupParent,proto3" json:"CgroupParent,omitempty"`
|
||||
Exports []*ExportEntry `protobuf:"bytes,11,rep,name=Exports,proto3" json:"Exports,omitempty"`
|
||||
ExtraHosts []string `protobuf:"bytes,12,rep,name=ExtraHosts,proto3" json:"ExtraHosts,omitempty"`
|
||||
Labels map[string]string `protobuf:"bytes,13,rep,name=Labels,proto3" json:"Labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
NetworkMode string `protobuf:"bytes,14,opt,name=NetworkMode,proto3" json:"NetworkMode,omitempty"`
|
||||
NoCacheFilter []string `protobuf:"bytes,15,rep,name=NoCacheFilter,proto3" json:"NoCacheFilter,omitempty"`
|
||||
Platforms []string `protobuf:"bytes,16,rep,name=Platforms,proto3" json:"Platforms,omitempty"`
|
||||
Secrets []*Secret `protobuf:"bytes,17,rep,name=Secrets,proto3" json:"Secrets,omitempty"`
|
||||
ShmSize int64 `protobuf:"varint,18,opt,name=ShmSize,proto3" json:"ShmSize,omitempty"`
|
||||
SSH []*SSH `protobuf:"bytes,19,rep,name=SSH,proto3" json:"SSH,omitempty"`
|
||||
Tags []string `protobuf:"bytes,20,rep,name=Tags,proto3" json:"Tags,omitempty"`
|
||||
Target string `protobuf:"bytes,21,opt,name=Target,proto3" json:"Target,omitempty"`
|
||||
Ulimits *UlimitOpt `protobuf:"bytes,22,opt,name=Ulimits,proto3" json:"Ulimits,omitempty"`
|
||||
Builder string `protobuf:"bytes,23,opt,name=Builder,proto3" json:"Builder,omitempty"`
|
||||
NoCache bool `protobuf:"varint,24,opt,name=NoCache,proto3" json:"NoCache,omitempty"`
|
||||
Pull bool `protobuf:"varint,25,opt,name=Pull,proto3" json:"Pull,omitempty"`
|
||||
ExportPush bool `protobuf:"varint,26,opt,name=ExportPush,proto3" json:"ExportPush,omitempty"`
|
||||
ExportLoad bool `protobuf:"varint,27,opt,name=ExportLoad,proto3" json:"ExportLoad,omitempty"`
|
||||
SourcePolicy *pb.Policy `protobuf:"bytes,28,opt,name=SourcePolicy,proto3" json:"SourcePolicy,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
ContextPath string `protobuf:"bytes,1,opt,name=ContextPath,proto3" json:"ContextPath,omitempty"`
|
||||
DockerfileName string `protobuf:"bytes,2,opt,name=DockerfileName,proto3" json:"DockerfileName,omitempty"`
|
||||
PrintFunc *PrintFunc `protobuf:"bytes,3,opt,name=PrintFunc,proto3" json:"PrintFunc,omitempty"`
|
||||
NamedContexts map[string]string `protobuf:"bytes,4,rep,name=NamedContexts,proto3" json:"NamedContexts,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
Allow []string `protobuf:"bytes,5,rep,name=Allow,proto3" json:"Allow,omitempty"`
|
||||
Attests []*Attest `protobuf:"bytes,6,rep,name=Attests,proto3" json:"Attests,omitempty"`
|
||||
BuildArgs map[string]string `protobuf:"bytes,7,rep,name=BuildArgs,proto3" json:"BuildArgs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
CacheFrom []*CacheOptionsEntry `protobuf:"bytes,8,rep,name=CacheFrom,proto3" json:"CacheFrom,omitempty"`
|
||||
CacheTo []*CacheOptionsEntry `protobuf:"bytes,9,rep,name=CacheTo,proto3" json:"CacheTo,omitempty"`
|
||||
CgroupParent string `protobuf:"bytes,10,opt,name=CgroupParent,proto3" json:"CgroupParent,omitempty"`
|
||||
Exports []*ExportEntry `protobuf:"bytes,11,rep,name=Exports,proto3" json:"Exports,omitempty"`
|
||||
ExtraHosts []string `protobuf:"bytes,12,rep,name=ExtraHosts,proto3" json:"ExtraHosts,omitempty"`
|
||||
Labels map[string]string `protobuf:"bytes,13,rep,name=Labels,proto3" json:"Labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
NetworkMode string `protobuf:"bytes,14,opt,name=NetworkMode,proto3" json:"NetworkMode,omitempty"`
|
||||
NoCacheFilter []string `protobuf:"bytes,15,rep,name=NoCacheFilter,proto3" json:"NoCacheFilter,omitempty"`
|
||||
Platforms []string `protobuf:"bytes,16,rep,name=Platforms,proto3" json:"Platforms,omitempty"`
|
||||
Secrets []*Secret `protobuf:"bytes,17,rep,name=Secrets,proto3" json:"Secrets,omitempty"`
|
||||
ShmSize int64 `protobuf:"varint,18,opt,name=ShmSize,proto3" json:"ShmSize,omitempty"`
|
||||
SSH []*SSH `protobuf:"bytes,19,rep,name=SSH,proto3" json:"SSH,omitempty"`
|
||||
Tags []string `protobuf:"bytes,20,rep,name=Tags,proto3" json:"Tags,omitempty"`
|
||||
Target string `protobuf:"bytes,21,opt,name=Target,proto3" json:"Target,omitempty"`
|
||||
Ulimits *UlimitOpt `protobuf:"bytes,22,opt,name=Ulimits,proto3" json:"Ulimits,omitempty"`
|
||||
Builder string `protobuf:"bytes,23,opt,name=Builder,proto3" json:"Builder,omitempty"`
|
||||
NoCache bool `protobuf:"varint,24,opt,name=NoCache,proto3" json:"NoCache,omitempty"`
|
||||
Pull bool `protobuf:"varint,25,opt,name=Pull,proto3" json:"Pull,omitempty"`
|
||||
ExportPush bool `protobuf:"varint,26,opt,name=ExportPush,proto3" json:"ExportPush,omitempty"`
|
||||
ExportLoad bool `protobuf:"varint,27,opt,name=ExportLoad,proto3" json:"ExportLoad,omitempty"`
|
||||
SourcePolicy *pb.Policy `protobuf:"bytes,28,opt,name=SourcePolicy,proto3" json:"SourcePolicy,omitempty"`
|
||||
Ref string `protobuf:"bytes,29,opt,name=Ref,proto3" json:"Ref,omitempty"`
|
||||
GroupRef string `protobuf:"bytes,30,opt,name=GroupRef,proto3" json:"GroupRef,omitempty"`
|
||||
Annotations []string `protobuf:"bytes,31,rep,name=Annotations,proto3" json:"Annotations,omitempty"`
|
||||
WithProvenanceResponse bool `protobuf:"varint,32,opt,name=WithProvenanceResponse,proto3" json:"WithProvenanceResponse,omitempty"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *BuildOptions) Reset() { *m = BuildOptions{} }
|
||||
@@ -524,6 +528,34 @@ func (m *BuildOptions) GetSourcePolicy() *pb.Policy {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *BuildOptions) GetRef() string {
|
||||
if m != nil {
|
||||
return m.Ref
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *BuildOptions) GetGroupRef() string {
|
||||
if m != nil {
|
||||
return m.GroupRef
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *BuildOptions) GetAnnotations() []string {
|
||||
if m != nil {
|
||||
return m.Annotations
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *BuildOptions) GetWithProvenanceResponse() bool {
|
||||
if m != nil {
|
||||
return m.WithProvenanceResponse
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type ExportEntry struct {
|
||||
Type string `protobuf:"bytes,1,opt,name=Type,proto3" json:"Type,omitempty"`
|
||||
Attrs map[string]string `protobuf:"bytes,2,rep,name=Attrs,proto3" json:"Attrs,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
@@ -1527,6 +1559,7 @@ func (m *InitMessage) GetInvokeConfig() *InvokeConfig {
|
||||
type InvokeConfig struct {
|
||||
Entrypoint []string `protobuf:"bytes,1,rep,name=Entrypoint,proto3" json:"Entrypoint,omitempty"`
|
||||
Cmd []string `protobuf:"bytes,2,rep,name=Cmd,proto3" json:"Cmd,omitempty"`
|
||||
NoCmd bool `protobuf:"varint,11,opt,name=NoCmd,proto3" json:"NoCmd,omitempty"`
|
||||
Env []string `protobuf:"bytes,3,rep,name=Env,proto3" json:"Env,omitempty"`
|
||||
User string `protobuf:"bytes,4,opt,name=User,proto3" json:"User,omitempty"`
|
||||
NoUser bool `protobuf:"varint,5,opt,name=NoUser,proto3" json:"NoUser,omitempty"`
|
||||
@@ -1578,6 +1611,13 @@ func (m *InvokeConfig) GetCmd() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *InvokeConfig) GetNoCmd() bool {
|
||||
if m != nil {
|
||||
return m.NoCmd
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (m *InvokeConfig) GetEnv() []string {
|
||||
if m != nil {
|
||||
return m.Env
|
||||
@@ -2046,125 +2086,129 @@ func init() {
|
||||
func init() { proto.RegisterFile("controller.proto", fileDescriptor_ed7f10298fa1d90f) }
|
||||
|
||||
var fileDescriptor_ed7f10298fa1d90f = []byte{
|
||||
// 1881 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0x5f, 0x6f, 0xdb, 0xc8,
|
||||
0x11, 0x2f, 0x25, 0x59, 0x7f, 0x46, 0x96, 0xe3, 0x6c, 0x9d, 0x74, 0xc3, 0xa4, 0x17, 0x87, 0x49,
|
||||
0xae, 0x42, 0x53, 0x48, 0x77, 0xbe, 0xa6, 0xbe, 0x5c, 0xee, 0x80, 0xda, 0xb2, 0x05, 0xfb, 0x90,
|
||||
0xd8, 0xc6, 0xca, 0xc9, 0xa1, 0x2d, 0xd0, 0x80, 0x92, 0xd6, 0x32, 0x21, 0x8a, 0xab, 0x72, 0x57,
|
||||
0xb6, 0xd5, 0xa7, 0xbe, 0xf4, 0xad, 0xe8, 0xf7, 0x28, 0xfa, 0x11, 0xfa, 0xd2, 0x7e, 0xa1, 0xa2,
|
||||
0x1f, 0xa1, 0xd8, 0x3f, 0xa4, 0x48, 0x4b, 0x94, 0xed, 0xf6, 0x49, 0x3b, 0xc3, 0xdf, 0x6f, 0x76,
|
||||
0x67, 0x38, 0x3b, 0x33, 0x14, 0xac, 0xf7, 0x58, 0x20, 0x42, 0xe6, 0xfb, 0x34, 0x6c, 0x8c, 0x43,
|
||||
0x26, 0x18, 0xda, 0xe8, 0x4e, 0x3c, 0xbf, 0x7f, 0xd5, 0x48, 0x3c, 0xb8, 0xf8, 0xd2, 0x7e, 0x3b,
|
||||
0xf0, 0xc4, 0xf9, 0xa4, 0xdb, 0xe8, 0xb1, 0x51, 0x73, 0xc4, 0xba, 0xd3, 0xa6, 0x42, 0x0d, 0x3d,
|
||||
0xd1, 0x74, 0xc7, 0x5e, 0x93, 0xd3, 0xf0, 0xc2, 0xeb, 0x51, 0xde, 0x34, 0xa4, 0xe8, 0x57, 0x9b,
|
||||
0xb4, 0x5f, 0x67, 0x92, 0x39, 0x9b, 0x84, 0x3d, 0x3a, 0x66, 0xbe, 0xd7, 0x9b, 0x36, 0xc7, 0xdd,
|
||||
0xa6, 0x5e, 0x69, 0x9a, 0x53, 0x87, 0x8d, 0x77, 0x1e, 0x17, 0x27, 0x21, 0xeb, 0x51, 0xce, 0x29,
|
||||
0x27, 0xf4, 0x0f, 0x13, 0xca, 0x05, 0x5a, 0x87, 0x3c, 0xa1, 0x67, 0xd8, 0xda, 0xb4, 0xea, 0x15,
|
||||
0x22, 0x97, 0xce, 0x09, 0x3c, 0xb8, 0x86, 0xe4, 0x63, 0x16, 0x70, 0x8a, 0xb6, 0x61, 0xe5, 0x30,
|
||||
0x38, 0x63, 0x1c, 0x5b, 0x9b, 0xf9, 0x7a, 0x75, 0xeb, 0x59, 0x63, 0x91, 0x73, 0x0d, 0xc3, 0x93,
|
||||
0x48, 0xa2, 0xf1, 0x0e, 0x87, 0x6a, 0x42, 0x8b, 0x9e, 0x40, 0x25, 0x12, 0xf7, 0xcc, 0xc6, 0x33,
|
||||
0x05, 0x6a, 0xc3, 0xea, 0x61, 0x70, 0xc1, 0x86, 0xb4, 0xc5, 0x82, 0x33, 0x6f, 0x80, 0x73, 0x9b,
|
||||
0x56, 0xbd, 0xba, 0xe5, 0x2c, 0xde, 0x2c, 0x89, 0x24, 0x29, 0x9e, 0xf3, 0x3d, 0xe0, 0x3d, 0x8f,
|
||||
0xf7, 0x58, 0x10, 0xd0, 0x5e, 0xe4, 0x4c, 0xa6, 0xd3, 0xe9, 0x33, 0xe5, 0xae, 0x9d, 0xc9, 0x79,
|
||||
0x0c, 0x8f, 0x16, 0xd8, 0xd2, 0x61, 0x71, 0x7e, 0x0f, 0xab, 0xbb, 0xf2, 0x6c, 0xd9, 0xc6, 0xbf,
|
||||
0x85, 0xd2, 0xf1, 0x58, 0x78, 0x2c, 0xe0, 0xcb, 0xbd, 0x51, 0x66, 0x0c, 0x92, 0x44, 0x14, 0xe7,
|
||||
0x9f, 0x55, 0xb3, 0x81, 0x51, 0xa0, 0x4d, 0xa8, 0xb6, 0x58, 0x20, 0xe8, 0x95, 0x38, 0x71, 0xc5,
|
||||
0xb9, 0xd9, 0x28, 0xa9, 0x42, 0x9f, 0xc3, 0xda, 0x1e, 0xeb, 0x0d, 0x69, 0x78, 0xe6, 0xf9, 0xf4,
|
||||
0xc8, 0x1d, 0x51, 0xe3, 0xd2, 0x35, 0x2d, 0xfa, 0x4e, 0x7a, 0xed, 0x05, 0xa2, 0x3d, 0x09, 0x7a,
|
||||
0x38, 0xaf, 0x8e, 0xf6, 0x34, 0xeb, 0xad, 0x1a, 0x18, 0x99, 0x31, 0xd0, 0xef, 0xa0, 0x26, 0xcd,
|
||||
0xf4, 0xcd, 0xd6, 0x1c, 0x17, 0x54, 0x62, 0xbc, 0xbe, 0xd9, 0xbb, 0x46, 0x8a, 0xb7, 0x1f, 0x88,
|
||||
0x70, 0x4a, 0xd2, 0xb6, 0xd0, 0x06, 0xac, 0xec, 0xf8, 0x3e, 0xbb, 0xc4, 0x2b, 0x9b, 0xf9, 0x7a,
|
||||
0x85, 0x68, 0x01, 0xfd, 0x0a, 0x4a, 0x3b, 0x42, 0x50, 0x2e, 0x38, 0x2e, 0xaa, 0xcd, 0x9e, 0x2c,
|
||||
0xde, 0x4c, 0x83, 0x48, 0x04, 0x46, 0xc7, 0x50, 0x51, 0xfb, 0xef, 0x84, 0x03, 0x8e, 0x4b, 0x8a,
|
||||
0xf9, 0xe5, 0x2d, 0x8e, 0x19, 0x73, 0xf4, 0x11, 0x67, 0x36, 0xd0, 0x3e, 0x54, 0x5a, 0x6e, 0xef,
|
||||
0x9c, 0xb6, 0x43, 0x36, 0xc2, 0x65, 0x65, 0xf0, 0x67, 0x8b, 0x0d, 0x2a, 0x98, 0x31, 0x68, 0xcc,
|
||||
0xc4, 0x4c, 0xb4, 0x03, 0x25, 0x25, 0x9c, 0x32, 0x5c, 0xb9, 0x9b, 0x91, 0x88, 0x87, 0x1c, 0x58,
|
||||
0x6d, 0x0d, 0x42, 0x36, 0x19, 0x9f, 0xb8, 0x21, 0x0d, 0x04, 0x06, 0xf5, 0xaa, 0x53, 0x3a, 0xf4,
|
||||
0x16, 0x4a, 0xfb, 0x57, 0x63, 0x16, 0x0a, 0x8e, 0xab, 0xcb, 0x2e, 0xaf, 0x06, 0x99, 0x0d, 0x0c,
|
||||
0x03, 0x7d, 0x06, 0xb0, 0x7f, 0x25, 0x42, 0xf7, 0x80, 0xc9, 0xb0, 0xaf, 0xaa, 0xd7, 0x91, 0xd0,
|
||||
0xa0, 0x36, 0x14, 0xdf, 0xb9, 0x5d, 0xea, 0x73, 0x5c, 0x53, 0xb6, 0x1b, 0xb7, 0x08, 0xac, 0x26,
|
||||
0xe8, 0x8d, 0x0c, 0x5b, 0xe6, 0xf5, 0x11, 0x15, 0x97, 0x2c, 0x1c, 0xbe, 0x67, 0x7d, 0x8a, 0xd7,
|
||||
0x74, 0x5e, 0x27, 0x54, 0xe8, 0x05, 0xd4, 0x8e, 0x98, 0x0e, 0x9e, 0xe7, 0x0b, 0x1a, 0xe2, 0x7b,
|
||||
0xea, 0x30, 0x69, 0xa5, 0xba, 0xcb, 0xbe, 0x2b, 0xce, 0x58, 0x38, 0xe2, 0x78, 0x5d, 0x21, 0x66,
|
||||
0x0a, 0x99, 0x41, 0x1d, 0xda, 0x0b, 0xa9, 0xe0, 0xf8, 0xfe, 0xb2, 0x0c, 0xd2, 0x20, 0x12, 0x81,
|
||||
0x11, 0x86, 0x52, 0xe7, 0x7c, 0xd4, 0xf1, 0xfe, 0x48, 0x31, 0xda, 0xb4, 0xea, 0x79, 0x12, 0x89,
|
||||
0xe8, 0x15, 0xe4, 0x3b, 0x9d, 0x03, 0xfc, 0x63, 0x65, 0xed, 0x51, 0x86, 0xb5, 0xce, 0x01, 0x91,
|
||||
0x28, 0x84, 0xa0, 0x70, 0xea, 0x0e, 0x38, 0xde, 0x50, 0xe7, 0x52, 0x6b, 0xf4, 0x10, 0x8a, 0xa7,
|
||||
0x6e, 0x38, 0xa0, 0x02, 0x3f, 0x50, 0x3e, 0x1b, 0x09, 0xbd, 0x81, 0xd2, 0x07, 0xdf, 0x1b, 0x79,
|
||||
0x82, 0xe3, 0x87, 0xcb, 0x2e, 0xa7, 0x06, 0x1d, 0x8f, 0x05, 0x89, 0xf0, 0xf2, 0xb4, 0x2a, 0xde,
|
||||
0x34, 0xc4, 0x3f, 0x51, 0x36, 0x23, 0x51, 0x3e, 0x31, 0xe1, 0xc2, 0x78, 0xd3, 0xaa, 0x97, 0x49,
|
||||
0x24, 0xca, 0xa3, 0x9d, 0x4c, 0x7c, 0x1f, 0x3f, 0x52, 0x6a, 0xb5, 0xd6, 0xef, 0x5e, 0xa6, 0xc1,
|
||||
0xc9, 0x84, 0x9f, 0x63, 0x5b, 0x3d, 0x49, 0x68, 0x66, 0xcf, 0xdf, 0x31, 0xb7, 0x8f, 0x1f, 0x27,
|
||||
0x9f, 0x4b, 0x0d, 0x3a, 0x84, 0xd5, 0x8e, 0x6a, 0x4b, 0x27, 0xaa, 0x19, 0xe1, 0x27, 0xca, 0x8f,
|
||||
0x97, 0x0d, 0xd9, 0xb9, 0x1a, 0x51, 0xe7, 0x92, 0x3e, 0x24, 0x9b, 0x57, 0x43, 0x83, 0x49, 0x8a,
|
||||
0x6a, 0xff, 0x1a, 0xd0, 0x7c, 0xd5, 0x90, 0xd5, 0x76, 0x48, 0xa7, 0x51, 0xb5, 0x1d, 0xd2, 0xa9,
|
||||
0x2c, 0x1c, 0x17, 0xae, 0x3f, 0x89, 0x6a, 0x9e, 0x16, 0xbe, 0xc9, 0x7d, 0x6d, 0xd9, 0xdf, 0xc2,
|
||||
0x5a, 0xfa, 0x42, 0xdf, 0x89, 0xfd, 0x06, 0xaa, 0x89, 0xac, 0xbd, 0x0b, 0xd5, 0xf9, 0x97, 0x05,
|
||||
0xd5, 0xc4, 0xd5, 0x52, 0x49, 0x30, 0x1d, 0x53, 0x43, 0x56, 0x6b, 0xb4, 0x0b, 0x2b, 0x3b, 0x42,
|
||||
0x84, 0xb2, 0x45, 0xc8, 0x3c, 0xfa, 0xc5, 0x8d, 0x17, 0xb4, 0xa1, 0xe0, 0xfa, 0x0a, 0x69, 0xaa,
|
||||
0xbc, 0x41, 0x7b, 0x94, 0x0b, 0x2f, 0x70, 0xe5, 0x2d, 0x53, 0x15, 0xbd, 0x42, 0x92, 0x2a, 0xfb,
|
||||
0x6b, 0x80, 0x19, 0xed, 0x4e, 0x3e, 0xfc, 0xdd, 0x82, 0xfb, 0x73, 0x55, 0x68, 0xa1, 0x27, 0x07,
|
||||
0x69, 0x4f, 0xb6, 0x6e, 0x59, 0xd1, 0xe6, 0xfd, 0xf9, 0x3f, 0x4e, 0x7b, 0x04, 0x45, 0x5d, 0xfa,
|
||||
0x17, 0x9e, 0xd0, 0x86, 0xf2, 0x9e, 0xc7, 0xdd, 0xae, 0x4f, 0xfb, 0x8a, 0x5a, 0x26, 0xb1, 0xac,
|
||||
0xfa, 0x8e, 0x3a, 0xbd, 0x8e, 0x9e, 0x16, 0x1c, 0x7d, 0xc7, 0xd1, 0x1a, 0xe4, 0xe2, 0x99, 0x25,
|
||||
0x77, 0xb8, 0x27, 0xc1, 0xb2, 0xe1, 0x6a, 0x57, 0x2b, 0x44, 0x0b, 0x4e, 0x1b, 0x8a, 0xba, 0x6a,
|
||||
0xcc, 0xe1, 0x6d, 0x28, 0xb7, 0x3d, 0x9f, 0xaa, 0xbe, 0xad, 0xcf, 0x1c, 0xcb, 0xd2, 0xbd, 0xfd,
|
||||
0xe0, 0xc2, 0x6c, 0x2b, 0x97, 0xce, 0x76, 0xa2, 0x3d, 0x4b, 0x3f, 0x54, 0x27, 0x37, 0x7e, 0xa8,
|
||||
0xfe, 0xfd, 0x10, 0x8a, 0x6d, 0x16, 0x8e, 0x5c, 0x61, 0x8c, 0x19, 0xc9, 0x71, 0x60, 0xed, 0x30,
|
||||
0xe0, 0x63, 0xda, 0x13, 0xd9, 0x63, 0xde, 0x31, 0xdc, 0x8b, 0x31, 0x66, 0xc0, 0x4b, 0xcc, 0x29,
|
||||
0xd6, 0xdd, 0xe7, 0x94, 0xbf, 0x59, 0x50, 0x89, 0x2b, 0x11, 0x6a, 0x41, 0x51, 0xbd, 0x8d, 0x68,
|
||||
0x5a, 0x7c, 0x75, 0x43, 0xe9, 0x6a, 0x7c, 0x54, 0x68, 0xd3, 0x11, 0x34, 0xd5, 0xfe, 0x01, 0xaa,
|
||||
0x09, 0xf5, 0x82, 0x04, 0xd8, 0x4a, 0x26, 0x40, 0x66, 0x29, 0xd7, 0x9b, 0x24, 0xd3, 0x63, 0x0f,
|
||||
0x8a, 0x5a, 0xb9, 0x30, 0xac, 0x08, 0x0a, 0x07, 0x6e, 0xa8, 0x53, 0x23, 0x4f, 0xd4, 0x5a, 0xea,
|
||||
0x3a, 0xec, 0x4c, 0xa8, 0xd7, 0x93, 0x27, 0x6a, 0xed, 0xfc, 0xc3, 0x82, 0x9a, 0x19, 0xfd, 0x4c,
|
||||
0x04, 0x29, 0xac, 0xeb, 0x1b, 0x4a, 0xc3, 0x48, 0x67, 0xfc, 0x7f, 0xb3, 0x24, 0x94, 0x11, 0xb4,
|
||||
0x71, 0x9d, 0xab, 0xa3, 0x31, 0x67, 0xd2, 0x6e, 0xc1, 0x83, 0x85, 0xd0, 0x3b, 0x5d, 0x91, 0x97,
|
||||
0x70, 0x7f, 0x36, 0xd4, 0x66, 0xe7, 0xc9, 0x06, 0xa0, 0x24, 0xcc, 0x0c, 0xbd, 0x4f, 0xa1, 0x2a,
|
||||
0x3f, 0x12, 0xb2, 0x69, 0x0e, 0xac, 0x6a, 0x80, 0x89, 0x0c, 0x82, 0xc2, 0x90, 0x4e, 0x75, 0x36,
|
||||
0x54, 0x88, 0x5a, 0x3b, 0x7f, 0xb5, 0xe4, 0xac, 0x3f, 0x9e, 0x88, 0xf7, 0x94, 0x73, 0x77, 0x20,
|
||||
0x13, 0xb0, 0x70, 0x18, 0x78, 0xc2, 0x64, 0xdf, 0xe7, 0x59, 0x33, 0xff, 0x78, 0x22, 0x24, 0xcc,
|
||||
0xb0, 0x0e, 0x7e, 0x44, 0x14, 0x0b, 0x6d, 0x43, 0x61, 0xcf, 0x15, 0xae, 0xc9, 0x85, 0x8c, 0x09,
|
||||
0x47, 0x22, 0x12, 0x44, 0x29, 0xee, 0x96, 0xe4, 0x87, 0xcd, 0x78, 0x22, 0x9c, 0x17, 0xb0, 0x7e,
|
||||
0xdd, 0xfa, 0x02, 0xd7, 0xbe, 0x82, 0x6a, 0xc2, 0x8a, 0xba, 0xb7, 0xc7, 0x6d, 0x05, 0x28, 0x13,
|
||||
0xb9, 0x94, 0xbe, 0xc6, 0x07, 0x59, 0xd5, 0x7b, 0x38, 0xf7, 0xa0, 0xa6, 0x4c, 0xc7, 0x11, 0xfc,
|
||||
0x53, 0x0e, 0x4a, 0x91, 0x89, 0xed, 0x94, 0xdf, 0xcf, 0xb2, 0xfc, 0x9e, 0x77, 0xf9, 0x35, 0x14,
|
||||
0x64, 0xfd, 0x30, 0x2e, 0x67, 0x8c, 0x07, 0xed, 0x7e, 0x82, 0x26, 0xe1, 0xe8, 0x3b, 0x28, 0x12,
|
||||
0xca, 0xe5, 0x28, 0xa3, 0x87, 0xfe, 0xe7, 0x8b, 0x89, 0x1a, 0x33, 0x23, 0x1b, 0x92, 0xa4, 0x77,
|
||||
0xbc, 0x41, 0xe0, 0xfa, 0xb8, 0xb0, 0x8c, 0xae, 0x31, 0x09, 0xba, 0x56, 0xcc, 0xc2, 0xfd, 0x67,
|
||||
0x0b, 0xaa, 0x4b, 0x43, 0xbd, 0xfc, 0xb3, 0x6c, 0xee, 0x53, 0x31, 0xff, 0x3f, 0x7e, 0x2a, 0xfe,
|
||||
0xdb, 0x4a, 0x1b, 0x52, 0x53, 0x8d, 0xbc, 0x4f, 0x63, 0xe6, 0x05, 0xc2, 0xa4, 0x6c, 0x42, 0x23,
|
||||
0x0f, 0xda, 0x1a, 0xf5, 0x4d, 0xd1, 0x97, 0xcb, 0x59, 0xf1, 0xce, 0x9b, 0xe2, 0x2d, 0x93, 0xe0,
|
||||
0x03, 0xa7, 0xa1, 0x0a, 0x51, 0x85, 0xa8, 0xb5, 0xac, 0xd7, 0x47, 0x4c, 0x69, 0x57, 0x54, 0xb6,
|
||||
0x18, 0x49, 0xd9, 0xbb, 0xec, 0xe3, 0xa2, 0x76, 0xbc, 0x75, 0xa9, 0xba, 0xd0, 0x11, 0x93, 0xba,
|
||||
0x92, 0x02, 0x6a, 0x41, 0xe2, 0x4e, 0xc5, 0x14, 0x97, 0x75, 0xaa, 0x9d, 0x8a, 0xa9, 0x6c, 0x28,
|
||||
0x84, 0xf9, 0x7e, 0xd7, 0xed, 0x0d, 0x71, 0x45, 0x77, 0xb2, 0x48, 0x96, 0x93, 0x9e, 0x8c, 0xae,
|
||||
0xe7, 0xfa, 0xea, 0x9b, 0xa0, 0x4c, 0x22, 0xd1, 0xd9, 0x81, 0x4a, 0x9c, 0x14, 0xb2, 0x47, 0xb5,
|
||||
0xfb, 0x2a, 0xe8, 0x35, 0x92, 0x6b, 0xf7, 0xa3, 0x7c, 0xce, 0xcd, 0xe7, 0x73, 0x3e, 0x91, 0xcf,
|
||||
0xdb, 0x50, 0x4b, 0xa5, 0x87, 0x04, 0x11, 0x76, 0xc9, 0x8d, 0x21, 0xb5, 0x96, 0xba, 0x16, 0xf3,
|
||||
0xf5, 0x57, 0x6f, 0x8d, 0xa8, 0xb5, 0xf3, 0x1c, 0x6a, 0xa9, 0xc4, 0x58, 0x54, 0x81, 0x9d, 0x67,
|
||||
0x50, 0xeb, 0x08, 0x57, 0x4c, 0x96, 0xfc, 0x4d, 0xf1, 0x1f, 0x0b, 0xd6, 0x22, 0x8c, 0xa9, 0x31,
|
||||
0xbf, 0x84, 0xf2, 0x05, 0x0d, 0x05, 0xbd, 0x8a, 0xbb, 0x0e, 0x9e, 0x1f, 0x34, 0x3f, 0x2a, 0x04,
|
||||
0x89, 0x91, 0xe8, 0x1b, 0x28, 0x73, 0x65, 0x87, 0x46, 0x13, 0xcb, 0x67, 0x59, 0x2c, 0xb3, 0x5f,
|
||||
0x8c, 0x47, 0x4d, 0x28, 0xf8, 0x6c, 0xc0, 0xd5, 0x7b, 0xaf, 0x6e, 0x3d, 0xce, 0xe2, 0xbd, 0x63,
|
||||
0x03, 0xa2, 0x80, 0xe8, 0x2d, 0x94, 0x2f, 0xdd, 0x30, 0xf0, 0x82, 0x41, 0xf4, 0xb5, 0xfc, 0x34,
|
||||
0x8b, 0xf4, 0x83, 0xc6, 0x91, 0x98, 0xe0, 0xd4, 0xe4, 0x75, 0x39, 0x63, 0x26, 0x26, 0xce, 0x6f,
|
||||
0x64, 0xd6, 0x4a, 0xd1, 0xb8, 0x7f, 0x08, 0x35, 0x9d, 0xf9, 0x1f, 0x69, 0xc8, 0xe5, 0xfc, 0x67,
|
||||
0x2d, 0xbb, 0x9d, 0xbb, 0x49, 0x28, 0x49, 0x33, 0x9d, 0x4f, 0xa6, 0xb1, 0x45, 0x0a, 0x99, 0x4b,
|
||||
0x63, 0xb7, 0x37, 0x74, 0x07, 0xd1, 0x7b, 0x8a, 0x44, 0xf9, 0xe4, 0xc2, 0xec, 0xa7, 0x2f, 0x68,
|
||||
0x24, 0xca, 0xdc, 0x0c, 0xe9, 0x85, 0xc7, 0x67, 0xa3, 0x68, 0x2c, 0x6f, 0xfd, 0xa5, 0x04, 0xd0,
|
||||
0x8a, 0xcf, 0x83, 0x4e, 0x60, 0x45, 0xed, 0x87, 0x9c, 0xa5, 0x6d, 0x52, 0xf9, 0x6d, 0x3f, 0xbf,
|
||||
0x45, 0x2b, 0x45, 0x1f, 0x65, 0xf2, 0xab, 0xf1, 0x06, 0xbd, 0xc8, 0x2a, 0x08, 0xc9, 0x09, 0xc9,
|
||||
0x7e, 0x79, 0x03, 0xca, 0xd8, 0xfd, 0x00, 0x45, 0x9d, 0x05, 0x28, 0xab, 0xea, 0x25, 0xf3, 0xd6,
|
||||
0x7e, 0xb1, 0x1c, 0xa4, 0x8d, 0x7e, 0x61, 0x21, 0x62, 0x6a, 0x22, 0x72, 0x96, 0x34, 0x3d, 0x73,
|
||||
0x63, 0xb2, 0x02, 0x90, 0xea, 0x2f, 0x75, 0x0b, 0x7d, 0x0f, 0x45, 0x5d, 0xd5, 0xd0, 0x4f, 0x17,
|
||||
0x13, 0x22, 0x7b, 0xcb, 0x1f, 0xd7, 0xad, 0x2f, 0x2c, 0xf4, 0x1e, 0x0a, 0xb2, 0x9d, 0xa3, 0x8c,
|
||||
0xde, 0x94, 0x98, 0x05, 0x6c, 0x67, 0x19, 0xc4, 0x44, 0xf1, 0x13, 0xc0, 0x6c, 0xa8, 0x40, 0x19,
|
||||
0xff, 0x79, 0xcc, 0x4d, 0x27, 0x76, 0xfd, 0x66, 0xa0, 0xd9, 0xe0, 0xbd, 0xec, 0xa8, 0x67, 0x0c,
|
||||
0x65, 0xf6, 0xd2, 0xf8, 0x1a, 0xd9, 0xce, 0x32, 0x88, 0x31, 0x77, 0x0e, 0xb5, 0xd4, 0x7f, 0xa2,
|
||||
0xe8, 0xe7, 0xd9, 0x4e, 0x5e, 0xff, 0x8b, 0xd5, 0x7e, 0x75, 0x2b, 0xac, 0xd9, 0x49, 0x24, 0xa7,
|
||||
0x32, 0xf3, 0x18, 0x35, 0x6e, 0xf2, 0x3b, 0xfd, 0xff, 0xa6, 0xdd, 0xbc, 0x35, 0x5e, 0xef, 0xba,
|
||||
0x5b, 0xf8, 0x6d, 0x6e, 0xdc, 0xed, 0x16, 0xd5, 0x5f, 0xc5, 0x5f, 0xfd, 0x37, 0x00, 0x00, 0xff,
|
||||
0xff, 0xc1, 0x4b, 0x2d, 0x65, 0xc8, 0x16, 0x00, 0x00,
|
||||
// 1946 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x58, 0x5f, 0x53, 0x1b, 0xc9,
|
||||
0x11, 0xcf, 0x4a, 0x42, 0x7f, 0x5a, 0x08, 0xe3, 0x09, 0x76, 0xc6, 0x6b, 0x9f, 0x8d, 0xd7, 0xf6,
|
||||
0x45, 0x15, 0xa7, 0xc4, 0x1d, 0x17, 0x1f, 0xe7, 0xf3, 0x5d, 0x55, 0x40, 0xa0, 0xc0, 0x95, 0x0d,
|
||||
0xd4, 0x0a, 0xdb, 0x95, 0xa4, 0x2a, 0xae, 0x45, 0x1a, 0xc4, 0x16, 0xcb, 0x8e, 0xb2, 0x33, 0x12,
|
||||
0x28, 0x4f, 0x79, 0x48, 0xde, 0x52, 0xf9, 0x1e, 0xa9, 0x7c, 0x84, 0x3c, 0xe5, 0x2d, 0x1f, 0x27,
|
||||
0x1f, 0x21, 0x35, 0x3d, 0xb3, 0xab, 0x15, 0xd2, 0x0a, 0xc8, 0x3d, 0x69, 0xba, 0xf7, 0xd7, 0xdd,
|
||||
0xd3, 0x3d, 0x3d, 0xdd, 0x3d, 0x82, 0xe5, 0x0e, 0x0f, 0x65, 0xc4, 0x83, 0x80, 0x45, 0x8d, 0x7e,
|
||||
0xc4, 0x25, 0x27, 0x2b, 0xc7, 0x03, 0x3f, 0xe8, 0x5e, 0x36, 0x52, 0x1f, 0x86, 0x5f, 0xda, 0x6f,
|
||||
0x7a, 0xbe, 0x3c, 0x1d, 0x1c, 0x37, 0x3a, 0xfc, 0x7c, 0xed, 0x9c, 0x1f, 0x8f, 0xd6, 0x10, 0x75,
|
||||
0xe6, 0xcb, 0x35, 0xaf, 0xef, 0xaf, 0x09, 0x16, 0x0d, 0xfd, 0x0e, 0x13, 0x6b, 0x46, 0x28, 0xfe,
|
||||
0xd5, 0x2a, 0xed, 0x57, 0x99, 0xc2, 0x82, 0x0f, 0xa2, 0x0e, 0xeb, 0xf3, 0xc0, 0xef, 0x8c, 0xd6,
|
||||
0xfa, 0xc7, 0x6b, 0x7a, 0xa5, 0xc5, 0x9c, 0x3a, 0xac, 0xbc, 0xf5, 0x85, 0x3c, 0x8c, 0x78, 0x87,
|
||||
0x09, 0xc1, 0x84, 0xcb, 0xfe, 0x38, 0x60, 0x42, 0x92, 0x65, 0xc8, 0xbb, 0xec, 0x84, 0x5a, 0xab,
|
||||
0x56, 0xbd, 0xe2, 0xaa, 0xa5, 0x73, 0x08, 0xf7, 0xae, 0x20, 0x45, 0x9f, 0x87, 0x82, 0x91, 0x0d,
|
||||
0x58, 0xd8, 0x0b, 0x4f, 0xb8, 0xa0, 0xd6, 0x6a, 0xbe, 0x5e, 0x5d, 0x7f, 0xda, 0x98, 0xe5, 0x5c,
|
||||
0xc3, 0xc8, 0x29, 0xa4, 0xab, 0xf1, 0x8e, 0x80, 0x6a, 0x8a, 0x4b, 0x1e, 0x41, 0x25, 0x26, 0xb7,
|
||||
0x8d, 0xe1, 0x31, 0x83, 0xb4, 0x60, 0x71, 0x2f, 0x1c, 0xf2, 0x33, 0xd6, 0xe4, 0xe1, 0x89, 0xdf,
|
||||
0xa3, 0xb9, 0x55, 0xab, 0x5e, 0x5d, 0x77, 0x66, 0x1b, 0x4b, 0x23, 0xdd, 0x09, 0x39, 0xe7, 0x07,
|
||||
0xa0, 0xdb, 0xbe, 0xe8, 0xf0, 0x30, 0x64, 0x9d, 0xd8, 0x99, 0x4c, 0xa7, 0x27, 0xf7, 0x94, 0xbb,
|
||||
0xb2, 0x27, 0xe7, 0x21, 0x3c, 0x98, 0xa1, 0x4b, 0x87, 0xc5, 0xf9, 0x03, 0x2c, 0x6e, 0xa9, 0xbd,
|
||||
0x65, 0x2b, 0xff, 0x0e, 0x4a, 0x07, 0x7d, 0xe9, 0xf3, 0x50, 0xcc, 0xf7, 0x06, 0xd5, 0x18, 0xa4,
|
||||
0x1b, 0x8b, 0x38, 0xff, 0x59, 0x34, 0x06, 0x0c, 0x83, 0xac, 0x42, 0xb5, 0xc9, 0x43, 0xc9, 0x2e,
|
||||
0xe5, 0xa1, 0x27, 0x4f, 0x8d, 0xa1, 0x34, 0x8b, 0x7c, 0x0e, 0x4b, 0xdb, 0xbc, 0x73, 0xc6, 0xa2,
|
||||
0x13, 0x3f, 0x60, 0xfb, 0xde, 0x39, 0x33, 0x2e, 0x5d, 0xe1, 0x92, 0xef, 0x95, 0xd7, 0x7e, 0x28,
|
||||
0x5b, 0x83, 0xb0, 0x43, 0xf3, 0xb8, 0xb5, 0x27, 0x59, 0xa7, 0x6a, 0x60, 0xee, 0x58, 0x82, 0xfc,
|
||||
0x1e, 0x6a, 0x4a, 0x4d, 0xd7, 0x98, 0x16, 0xb4, 0x80, 0x89, 0xf1, 0xea, 0x7a, 0xef, 0x1a, 0x13,
|
||||
0x72, 0x3b, 0xa1, 0x8c, 0x46, 0xee, 0xa4, 0x2e, 0xb2, 0x02, 0x0b, 0x9b, 0x41, 0xc0, 0x2f, 0xe8,
|
||||
0xc2, 0x6a, 0xbe, 0x5e, 0x71, 0x35, 0x41, 0xbe, 0x86, 0xd2, 0xa6, 0x94, 0x4c, 0x48, 0x41, 0x8b,
|
||||
0x68, 0xec, 0xd1, 0x6c, 0x63, 0x1a, 0xe4, 0xc6, 0x60, 0x72, 0x00, 0x15, 0xb4, 0xbf, 0x19, 0xf5,
|
||||
0x04, 0x2d, 0xa1, 0xe4, 0x97, 0x37, 0xd8, 0x66, 0x22, 0xa3, 0xb7, 0x38, 0xd6, 0x41, 0x76, 0xa0,
|
||||
0xd2, 0xf4, 0x3a, 0xa7, 0xac, 0x15, 0xf1, 0x73, 0x5a, 0x46, 0x85, 0x3f, 0x9f, 0xad, 0x10, 0x61,
|
||||
0x46, 0xa1, 0x51, 0x93, 0x48, 0x92, 0x4d, 0x28, 0x21, 0x71, 0xc4, 0x69, 0xe5, 0x76, 0x4a, 0x62,
|
||||
0x39, 0xe2, 0xc0, 0x62, 0xb3, 0x17, 0xf1, 0x41, 0xff, 0xd0, 0x8b, 0x58, 0x28, 0x29, 0xe0, 0x51,
|
||||
0x4f, 0xf0, 0xc8, 0x1b, 0x28, 0xed, 0x5c, 0xf6, 0x79, 0x24, 0x05, 0xad, 0xce, 0xbb, 0xbc, 0x1a,
|
||||
0x64, 0x0c, 0x18, 0x09, 0xf2, 0x18, 0x60, 0xe7, 0x52, 0x46, 0xde, 0x2e, 0x57, 0x61, 0x5f, 0xc4,
|
||||
0xe3, 0x48, 0x71, 0x48, 0x0b, 0x8a, 0x6f, 0xbd, 0x63, 0x16, 0x08, 0x5a, 0x43, 0xdd, 0x8d, 0x1b,
|
||||
0x04, 0x56, 0x0b, 0x68, 0x43, 0x46, 0x5a, 0xe5, 0xf5, 0x3e, 0x93, 0x17, 0x3c, 0x3a, 0x7b, 0xc7,
|
||||
0xbb, 0x8c, 0x2e, 0xe9, 0xbc, 0x4e, 0xb1, 0xc8, 0x73, 0xa8, 0xed, 0x73, 0x1d, 0x3c, 0x3f, 0x90,
|
||||
0x2c, 0xa2, 0x77, 0x70, 0x33, 0x93, 0x4c, 0xbc, 0xcb, 0x81, 0x27, 0x4f, 0x78, 0x74, 0x2e, 0xe8,
|
||||
0x32, 0x22, 0xc6, 0x0c, 0x95, 0x41, 0x6d, 0xd6, 0x89, 0x98, 0x14, 0xf4, 0xee, 0xbc, 0x0c, 0xd2,
|
||||
0x20, 0x37, 0x06, 0x13, 0x0a, 0xa5, 0xf6, 0xe9, 0x79, 0xdb, 0xff, 0x13, 0xa3, 0x64, 0xd5, 0xaa,
|
||||
0xe7, 0xdd, 0x98, 0x24, 0x2f, 0x21, 0xdf, 0x6e, 0xef, 0xd2, 0x9f, 0xa2, 0xb6, 0x07, 0x19, 0xda,
|
||||
0xda, 0xbb, 0xae, 0x42, 0x11, 0x02, 0x85, 0x23, 0xaf, 0x27, 0xe8, 0x0a, 0xee, 0x0b, 0xd7, 0xe4,
|
||||
0x3e, 0x14, 0x8f, 0xbc, 0xa8, 0xc7, 0x24, 0xbd, 0x87, 0x3e, 0x1b, 0x8a, 0xbc, 0x86, 0xd2, 0xfb,
|
||||
0xc0, 0x3f, 0xf7, 0xa5, 0xa0, 0xf7, 0xe7, 0x5d, 0x4e, 0x0d, 0x3a, 0xe8, 0x4b, 0x37, 0xc6, 0xab,
|
||||
0xdd, 0x62, 0xbc, 0x59, 0x44, 0x7f, 0x86, 0x3a, 0x63, 0x52, 0x7d, 0x31, 0xe1, 0xa2, 0x74, 0xd5,
|
||||
0xaa, 0x97, 0xdd, 0x98, 0x54, 0x5b, 0x3b, 0x1c, 0x04, 0x01, 0x7d, 0x80, 0x6c, 0x5c, 0xeb, 0xb3,
|
||||
0x57, 0x69, 0x70, 0x38, 0x10, 0xa7, 0xd4, 0xc6, 0x2f, 0x29, 0xce, 0xf8, 0xfb, 0x5b, 0xee, 0x75,
|
||||
0xe9, 0xc3, 0xf4, 0x77, 0xc5, 0x21, 0x7b, 0xb0, 0xd8, 0xc6, 0xb6, 0x74, 0x88, 0xcd, 0x88, 0x3e,
|
||||
0x42, 0x3f, 0x5e, 0x34, 0x54, 0xe7, 0x6a, 0xc4, 0x9d, 0x4b, 0xf9, 0x90, 0x6e, 0x5e, 0x0d, 0x0d,
|
||||
0x76, 0x27, 0x44, 0xe3, 0xba, 0xfa, 0xd9, 0xb8, 0xae, 0xda, 0x50, 0xfe, 0x8d, 0x4a, 0x72, 0xc5,
|
||||
0x7e, 0x8c, 0xec, 0x84, 0x56, 0xc9, 0xb4, 0x19, 0x86, 0x5c, 0x7a, 0xba, 0xee, 0x3e, 0xc1, 0x70,
|
||||
0xa7, 0x59, 0xe4, 0x6b, 0xb8, 0xff, 0xd1, 0x97, 0xa7, 0x87, 0x11, 0x1f, 0xb2, 0xd0, 0x0b, 0x3b,
|
||||
0x2c, 0xae, 0xe8, 0x74, 0x15, 0xdd, 0xc8, 0xf8, 0x6a, 0xff, 0x1a, 0xc8, 0x74, 0xf5, 0x52, 0xbb,
|
||||
0x3b, 0x63, 0xa3, 0xb8, 0xea, 0x9f, 0xb1, 0x91, 0x2a, 0x60, 0x43, 0x2f, 0x18, 0xc4, 0xb5, 0x57,
|
||||
0x13, 0xdf, 0xe6, 0xbe, 0xb1, 0xec, 0xef, 0x60, 0x69, 0xb2, 0xb0, 0xdc, 0x4a, 0xfa, 0x35, 0x54,
|
||||
0x53, 0xb7, 0xe7, 0x36, 0xa2, 0xce, 0xbf, 0x2d, 0xa8, 0xa6, 0xae, 0x38, 0x26, 0xe3, 0xa8, 0xcf,
|
||||
0x8c, 0x30, 0xae, 0xc9, 0x16, 0x2c, 0x6c, 0x4a, 0x19, 0xa9, 0x56, 0xa5, 0xf2, 0xf9, 0x97, 0xd7,
|
||||
0x16, 0x8a, 0x06, 0xc2, 0xf5, 0x55, 0xd6, 0xa2, 0x2a, 0xf8, 0xdb, 0x4c, 0x48, 0x3f, 0xc4, 0x50,
|
||||
0x63, 0x67, 0xa9, 0xb8, 0x69, 0x96, 0xfd, 0x0d, 0xc0, 0x58, 0xec, 0x56, 0x3e, 0xfc, 0xd3, 0x82,
|
||||
0xbb, 0x53, 0xd5, 0x70, 0xa6, 0x27, 0xbb, 0x93, 0x9e, 0xac, 0xdf, 0xb0, 0xb2, 0x4e, 0xfb, 0xf3,
|
||||
0x23, 0x76, 0xbb, 0x0f, 0x45, 0xdd, 0x82, 0x66, 0xee, 0xd0, 0x86, 0xf2, 0xb6, 0x2f, 0xbc, 0xe3,
|
||||
0x80, 0x75, 0x51, 0xb4, 0xec, 0x26, 0x34, 0xf6, 0x3f, 0xdc, 0xbd, 0x8e, 0x9e, 0x26, 0x1c, 0x5d,
|
||||
0x6b, 0xc8, 0x12, 0xe4, 0x92, 0xd9, 0x29, 0xb7, 0xb7, 0xad, 0xc0, 0xaa, 0xf1, 0x6b, 0x57, 0x2b,
|
||||
0xae, 0x26, 0x9c, 0x16, 0x14, 0x75, 0xf5, 0x9a, 0xc2, 0xdb, 0x50, 0x6e, 0xf9, 0x01, 0xc3, 0xf9,
|
||||
0x41, 0xef, 0x39, 0xa1, 0x95, 0x7b, 0x3b, 0xe1, 0xd0, 0x98, 0x55, 0x4b, 0x67, 0x23, 0x35, 0x26,
|
||||
0x28, 0x3f, 0x70, 0xa2, 0x30, 0x7e, 0xe0, 0x1c, 0x71, 0x1f, 0x8a, 0x2d, 0x1e, 0x9d, 0x7b, 0xd2,
|
||||
0x28, 0x33, 0x94, 0xe3, 0xc0, 0xd2, 0x5e, 0x28, 0xfa, 0xac, 0x23, 0xb3, 0xc7, 0xcd, 0x03, 0xb8,
|
||||
0x93, 0x60, 0xcc, 0xa0, 0x99, 0x9a, 0x97, 0xac, 0xdb, 0xcf, 0x4b, 0xff, 0xb0, 0xa0, 0x92, 0x54,
|
||||
0x44, 0xd2, 0x84, 0x22, 0x9e, 0x46, 0x3c, 0xb5, 0xbe, 0xbc, 0xa6, 0x84, 0x36, 0x3e, 0x20, 0xda,
|
||||
0x74, 0x26, 0x2d, 0x6a, 0x7f, 0x84, 0x6a, 0x8a, 0x3d, 0x23, 0x01, 0xd6, 0xd3, 0x09, 0x90, 0xd9,
|
||||
0x52, 0xb4, 0x91, 0x74, 0x7a, 0x6c, 0x43, 0x51, 0x33, 0x67, 0x86, 0x95, 0x40, 0x61, 0xd7, 0x8b,
|
||||
0x74, 0x6a, 0xe4, 0x5d, 0x5c, 0x2b, 0x5e, 0x9b, 0x9f, 0x48, 0x3c, 0x9e, 0xbc, 0x8b, 0x6b, 0xe7,
|
||||
0x5f, 0x16, 0xd4, 0xcc, 0x08, 0x6a, 0x22, 0xc8, 0x60, 0x59, 0xdf, 0x50, 0x16, 0x25, 0x55, 0x4d,
|
||||
0xfb, 0xff, 0x7a, 0x4e, 0x28, 0x63, 0x68, 0xe3, 0xaa, 0xac, 0x8e, 0xc6, 0x94, 0x4a, 0xbb, 0x09,
|
||||
0xf7, 0x66, 0x42, 0x6f, 0x75, 0x45, 0x5e, 0xc0, 0xdd, 0xf1, 0x70, 0x9d, 0x9d, 0x27, 0x2b, 0x40,
|
||||
0xd2, 0x30, 0x33, 0x7c, 0x3f, 0x81, 0xaa, 0x7a, 0xac, 0x64, 0x8b, 0x39, 0xb0, 0xa8, 0x01, 0x26,
|
||||
0x32, 0x04, 0x0a, 0x67, 0x6c, 0xa4, 0xb3, 0xa1, 0xe2, 0xe2, 0xda, 0xf9, 0xbb, 0xa5, 0xde, 0x1c,
|
||||
0xfd, 0x81, 0x7c, 0xc7, 0x84, 0xf0, 0x7a, 0x2a, 0x01, 0x0b, 0x7b, 0xa1, 0x2f, 0x4d, 0xf6, 0x7d,
|
||||
0x9e, 0xf5, 0xf6, 0xe8, 0x0f, 0xa4, 0x82, 0x19, 0xa9, 0xdd, 0x9f, 0xb8, 0x28, 0x45, 0x36, 0xa0,
|
||||
0xb0, 0xed, 0x49, 0xcf, 0xe4, 0x42, 0xc6, 0xa4, 0xa5, 0x10, 0x29, 0x41, 0x45, 0x6e, 0x95, 0xd4,
|
||||
0x03, 0xab, 0x3f, 0x90, 0xce, 0x73, 0x58, 0xbe, 0xaa, 0x7d, 0x86, 0x6b, 0x5f, 0x41, 0x35, 0xa5,
|
||||
0x05, 0xef, 0xed, 0x41, 0x0b, 0x01, 0x65, 0x57, 0x2d, 0x95, 0xaf, 0xc9, 0x46, 0x16, 0xb5, 0x0d,
|
||||
0xe7, 0x0e, 0xd4, 0x50, 0x75, 0x12, 0xc1, 0x3f, 0xe7, 0xa0, 0x14, 0xab, 0xd8, 0x98, 0xf0, 0xfb,
|
||||
0x69, 0x96, 0xdf, 0xd3, 0x2e, 0xbf, 0x82, 0x82, 0xaa, 0x1f, 0xc6, 0xe5, 0x8c, 0x31, 0xa5, 0xd5,
|
||||
0x4d, 0x89, 0x29, 0x38, 0xf9, 0x1e, 0x8a, 0x2e, 0x13, 0x6a, 0xa4, 0xd2, 0x8f, 0x8f, 0x67, 0xb3,
|
||||
0x05, 0x35, 0x66, 0x2c, 0x6c, 0x84, 0x94, 0x78, 0xdb, 0xef, 0x85, 0x5e, 0x40, 0x0b, 0xf3, 0xc4,
|
||||
0x35, 0x26, 0x25, 0xae, 0x19, 0xe3, 0x70, 0xff, 0xd5, 0x82, 0xea, 0xdc, 0x50, 0xcf, 0x7f, 0x1e,
|
||||
0x4e, 0x3d, 0x59, 0xf3, 0xff, 0xe7, 0x93, 0xf5, 0x2f, 0xb9, 0x49, 0x45, 0x38, 0x5d, 0xa9, 0xfb,
|
||||
0xd4, 0xe7, 0x7e, 0x28, 0x4d, 0xca, 0xa6, 0x38, 0x6a, 0xa3, 0xcd, 0xf3, 0xae, 0x29, 0xfa, 0x6a,
|
||||
0xa9, 0xae, 0xd9, 0x3e, 0x57, 0xbc, 0x2a, 0xa6, 0x81, 0x26, 0xc6, 0x25, 0x3d, 0x6f, 0x4a, 0xba,
|
||||
0x4a, 0x8d, 0xf7, 0x82, 0x45, 0x18, 0xb8, 0x8a, 0x8b, 0x6b, 0x55, 0xc5, 0xf7, 0x39, 0x72, 0x17,
|
||||
0x50, 0xd8, 0x50, 0x68, 0xe5, 0xa2, 0x4b, 0x8b, 0x3a, 0x1c, 0xcd, 0x8b, 0xd8, 0xca, 0x45, 0x97,
|
||||
0x96, 0x12, 0x2b, 0x17, 0x68, 0xe5, 0x48, 0x8e, 0x68, 0x59, 0x27, 0xe0, 0x91, 0x1c, 0xa9, 0x36,
|
||||
0xe3, 0xf2, 0x20, 0x38, 0xf6, 0x3a, 0x67, 0xb4, 0xa2, 0xfb, 0x5b, 0x4c, 0xab, 0x39, 0x54, 0xc5,
|
||||
0xdc, 0xf7, 0x02, 0x7c, 0xb1, 0x94, 0xdd, 0x98, 0x74, 0x36, 0xa1, 0x92, 0xa4, 0x8a, 0xea, 0x5c,
|
||||
0xad, 0x2e, 0x1e, 0x45, 0xcd, 0xcd, 0xb5, 0xba, 0x71, 0x96, 0xe7, 0xa6, 0xb3, 0x3c, 0x9f, 0xca,
|
||||
0xf2, 0x0d, 0xa8, 0x4d, 0x24, 0x8d, 0x02, 0xb9, 0xfc, 0x42, 0x18, 0x45, 0xb8, 0x56, 0xbc, 0x26,
|
||||
0x0f, 0xf4, 0x9b, 0xbc, 0xe6, 0xe2, 0xda, 0x79, 0x06, 0xb5, 0x89, 0x74, 0x99, 0x55, 0x97, 0x9d,
|
||||
0xa7, 0x50, 0x6b, 0x4b, 0x4f, 0x0e, 0xe6, 0xfc, 0x89, 0xf2, 0x5f, 0x0b, 0x96, 0x62, 0x8c, 0xa9,
|
||||
0x3c, 0xbf, 0x82, 0xf2, 0x90, 0x45, 0x92, 0x5d, 0x26, 0xbd, 0x88, 0x4e, 0x8f, 0xc1, 0x1f, 0x10,
|
||||
0xe1, 0x26, 0x48, 0xf2, 0x2d, 0x94, 0x05, 0xea, 0x61, 0xf1, 0x1c, 0xf3, 0x38, 0x4b, 0xca, 0xd8,
|
||||
0x4b, 0xf0, 0x64, 0x0d, 0x0a, 0x01, 0xef, 0x09, 0x3c, 0xf7, 0xea, 0xfa, 0xc3, 0x2c, 0xb9, 0xb7,
|
||||
0xbc, 0xe7, 0x22, 0x90, 0xbc, 0x81, 0xf2, 0x85, 0x17, 0x85, 0x7e, 0xd8, 0x8b, 0xdf, 0xf2, 0x4f,
|
||||
0xb2, 0x84, 0x3e, 0x6a, 0x9c, 0x9b, 0x08, 0x38, 0x35, 0x75, 0x89, 0x4e, 0xb8, 0x89, 0x89, 0xf3,
|
||||
0x5b, 0x95, 0xcb, 0x8a, 0x34, 0xee, 0xef, 0x41, 0x4d, 0xdf, 0x87, 0x0f, 0x2c, 0x12, 0x6a, 0x2a,
|
||||
0xb4, 0xe6, 0xdd, 0xd9, 0xad, 0x34, 0xd4, 0x9d, 0x94, 0x74, 0x3e, 0x99, 0x76, 0x17, 0x33, 0x54,
|
||||
0x2e, 0xf5, 0xbd, 0xce, 0x99, 0xd7, 0x8b, 0xcf, 0x29, 0x26, 0xd5, 0x97, 0xa1, 0xb1, 0xa7, 0xaf,
|
||||
0x6d, 0x4c, 0xaa, 0xdc, 0x8c, 0xd8, 0xd0, 0x17, 0xe3, 0x01, 0x35, 0xa1, 0xd7, 0xff, 0x56, 0x02,
|
||||
0x68, 0x26, 0xfb, 0x21, 0x87, 0xb0, 0x80, 0xf6, 0x88, 0x33, 0xb7, 0x79, 0xa2, 0xdf, 0xf6, 0xb3,
|
||||
0x1b, 0x34, 0x58, 0xf2, 0x41, 0x25, 0x3f, 0x0e, 0x3d, 0xe4, 0x79, 0x56, 0x99, 0x48, 0xcf, 0x4d,
|
||||
0xf6, 0x8b, 0x6b, 0x50, 0x46, 0xef, 0x7b, 0x28, 0xea, 0x2c, 0x20, 0x59, 0xb5, 0x30, 0x9d, 0xb7,
|
||||
0xf6, 0xf3, 0xf9, 0x20, 0xad, 0xf4, 0x0b, 0x8b, 0xb8, 0xa6, 0x52, 0x12, 0x67, 0x4e, 0x2b, 0x34,
|
||||
0x37, 0x26, 0x2b, 0x00, 0x13, 0x5d, 0xa7, 0x6e, 0x91, 0x1f, 0xa0, 0xa8, 0x6b, 0x1d, 0xf9, 0x6c,
|
||||
0xb6, 0x40, 0xac, 0x6f, 0xfe, 0xe7, 0xba, 0xf5, 0x85, 0x45, 0xde, 0x41, 0x41, 0x35, 0x79, 0x92,
|
||||
0xd1, 0xb1, 0x52, 0x13, 0x82, 0xed, 0xcc, 0x83, 0x98, 0x28, 0x7e, 0x02, 0x18, 0x8f, 0x1a, 0x24,
|
||||
0xe3, 0x1f, 0x99, 0xa9, 0x99, 0xc5, 0xae, 0x5f, 0x0f, 0x34, 0x06, 0xde, 0xa9, 0x3e, 0x7b, 0xc2,
|
||||
0x49, 0x66, 0x87, 0x4d, 0xae, 0x91, 0xed, 0xcc, 0x83, 0x18, 0x75, 0xa7, 0x50, 0x9b, 0xf8, 0xc7,
|
||||
0x96, 0xfc, 0x22, 0xdb, 0xc9, 0xab, 0x7f, 0x00, 0xdb, 0x2f, 0x6f, 0x84, 0x35, 0x96, 0x64, 0x7a,
|
||||
0x56, 0x33, 0x9f, 0x49, 0xe3, 0x3a, 0xbf, 0x27, 0xff, 0x7d, 0xb5, 0xd7, 0x6e, 0x8c, 0xd7, 0x56,
|
||||
0xb7, 0x0a, 0xbf, 0xcb, 0xf5, 0x8f, 0x8f, 0x8b, 0xf8, 0x47, 0xf6, 0x57, 0xff, 0x0b, 0x00, 0x00,
|
||||
0xff, 0xff, 0xf1, 0x59, 0xad, 0xb5, 0x66, 0x17, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
|
||||
@@ -77,6 +77,10 @@ message BuildOptions {
|
||||
bool ExportPush = 26;
|
||||
bool ExportLoad = 27;
|
||||
moby.buildkit.v1.sourcepolicy.Policy SourcePolicy = 28;
|
||||
string Ref = 29;
|
||||
string GroupRef = 30;
|
||||
repeated string Annotations = 31;
|
||||
bool WithProvenanceResponse = 32;
|
||||
}
|
||||
|
||||
message ExportEntry {
|
||||
@@ -192,6 +196,7 @@ message InitMessage {
|
||||
message InvokeConfig {
|
||||
repeated string Entrypoint = 1;
|
||||
repeated string Cmd = 2;
|
||||
bool NoCmd = 11; // Do not set cmd but use the image's default
|
||||
repeated string Env = 3;
|
||||
string User = 4;
|
||||
bool NoUser = 5; // Do not set user but use the image's default
|
||||
|
||||
@@ -15,6 +15,7 @@ func CreateExports(entries []*ExportEntry) ([]client.ExportEntry, error) {
|
||||
if len(entries) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var stdoutUsed bool
|
||||
for _, entry := range entries {
|
||||
if entry.Type == "" {
|
||||
return nil, errors.Errorf("type is required for output")
|
||||
@@ -68,10 +69,14 @@ func CreateExports(entries []*ExportEntry) ([]client.ExportEntry, error) {
|
||||
entry.Destination = "-"
|
||||
}
|
||||
if entry.Destination == "-" {
|
||||
if stdoutUsed {
|
||||
return nil, errors.Errorf("multiple outputs configured to write to stdout")
|
||||
}
|
||||
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
|
||||
return nil, errors.Errorf("dest file is required for %s exporter. refusing to write to console", out.Type)
|
||||
}
|
||||
out.Output = wrapWriteCloser(os.Stdout)
|
||||
stdoutUsed = true
|
||||
} else if entry.Destination != "" {
|
||||
fi, err := os.Stat(entry.Destination)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
|
||||
@@ -236,6 +236,7 @@ func TestResolvePaths(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ResolveOptionPaths(&tt.options)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -137,7 +137,7 @@ func (m *Manager) StartProcess(pid string, resultCtx *build.ResultHandle, cfg *p
|
||||
go func() {
|
||||
var err error
|
||||
if err = ctr.Exec(ctx, cfg, in.Stdin, in.Stdout, in.Stderr); err != nil {
|
||||
logrus.Errorf("failed to exec process: %v", err)
|
||||
logrus.Debugf("process error: %v", err)
|
||||
}
|
||||
logrus.Debugf("finished process %s %v", pid, cfg.Entrypoint)
|
||||
m.processes.Delete(pid)
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/log"
|
||||
"github.com/docker/buildx/build"
|
||||
cbuild "github.com/docker/buildx/controller/build"
|
||||
"github.com/docker/buildx/controller/control"
|
||||
|
||||
@@ -57,9 +57,7 @@ func (m *Server) ListProcesses(ctx context.Context, req *pb.ListProcessesRequest
|
||||
return nil, errors.Errorf("unknown ref %q", req.Ref)
|
||||
}
|
||||
res = new(pb.ListProcessesResponse)
|
||||
for _, p := range s.processes.ListProcesses() {
|
||||
res.Infos = append(res.Infos, p)
|
||||
}
|
||||
res.Infos = append(res.Infos, s.processes.ListProcesses()...)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
variable "GO_VERSION" {
|
||||
default = "1.20"
|
||||
default = null
|
||||
}
|
||||
variable "DOCS_FORMATS" {
|
||||
default = "md"
|
||||
@@ -7,6 +7,9 @@ variable "DOCS_FORMATS" {
|
||||
variable "DESTDIR" {
|
||||
default = "./bin"
|
||||
}
|
||||
variable "GOLANGCI_LINT_MULTIPLATFORM" {
|
||||
default = null
|
||||
}
|
||||
|
||||
# Special target: https://github.com/docker/metadata-action#bake-definition
|
||||
target "meta-helper" {
|
||||
@@ -32,6 +35,17 @@ target "lint" {
|
||||
inherits = ["_common"]
|
||||
dockerfile = "./hack/dockerfiles/lint.Dockerfile"
|
||||
output = ["type=cacheonly"]
|
||||
platforms = GOLANGCI_LINT_MULTIPLATFORM != null ? [
|
||||
"darwin/amd64",
|
||||
"darwin/arm64",
|
||||
"linux/amd64",
|
||||
"linux/arm64",
|
||||
"linux/s390x",
|
||||
"linux/ppc64le",
|
||||
"linux/riscv64",
|
||||
"windows/amd64",
|
||||
"windows/arm64"
|
||||
] : []
|
||||
}
|
||||
|
||||
target "validate-vendor" {
|
||||
@@ -166,6 +180,9 @@ variable "HTTPS_PROXY" {
|
||||
variable "NO_PROXY" {
|
||||
default = ""
|
||||
}
|
||||
variable "TEST_BUILDKIT_TAG" {
|
||||
default = null
|
||||
}
|
||||
|
||||
target "integration-test-base" {
|
||||
inherits = ["_common"]
|
||||
@@ -173,6 +190,7 @@ target "integration-test-base" {
|
||||
HTTP_PROXY = HTTP_PROXY
|
||||
HTTPS_PROXY = HTTPS_PROXY
|
||||
NO_PROXY = NO_PROXY
|
||||
BUILDKIT_VERSION = TEST_BUILDKIT_TAG
|
||||
}
|
||||
target = "integration-test-base"
|
||||
output = ["type=cacheonly"]
|
||||
|
||||
@@ -12,18 +12,118 @@ You can define your Bake file in the following file formats:
|
||||
|
||||
By default, Bake uses the following lookup order to find the configuration file:
|
||||
|
||||
1. `docker-bake.override.hcl`
|
||||
2. `docker-bake.hcl`
|
||||
3. `docker-bake.override.json`
|
||||
4. `docker-bake.json`
|
||||
5. `docker-compose.yaml`
|
||||
6. `docker-compose.yml`
|
||||
1. `compose.yaml`
|
||||
2. `compose.yml`
|
||||
3. `docker-compose.yml`
|
||||
4. `docker-compose.yaml`
|
||||
5. `docker-bake.json`
|
||||
6. `docker-bake.override.json`
|
||||
7. `docker-bake.hcl`
|
||||
8. `docker-bake.override.hcl`
|
||||
|
||||
Bake searches for the file in the current working directory.
|
||||
You can specify the file location explicitly using the `--file` flag:
|
||||
|
||||
```console
|
||||
$ docker buildx bake --file=../docker/bake.hcl --print
|
||||
$ docker buildx bake --file ../docker/bake.hcl --print
|
||||
```
|
||||
|
||||
If you don't specify a file explicitly, Bake searches for the file in the
|
||||
current working directory. If more than one Bake file is found, all files are
|
||||
merged into a single definition. Files are merged according to the lookup
|
||||
order. That means that if your project contains both a `compose.yaml` file and
|
||||
a `docker-bake.hcl` file, Bake loads the `compose.yaml` file first, and then
|
||||
the `docker-bake.hcl` file.
|
||||
|
||||
If merged files contain duplicate attribute definitions, those definitions are
|
||||
either merged or overridden by the last occurrence, depending on the attribute.
|
||||
The following attributes are overridden by the last occurrence:
|
||||
|
||||
- `target.cache-to`
|
||||
- `target.dockerfile-inline`
|
||||
- `target.dockerfile`
|
||||
- `target.outputs`
|
||||
- `target.platforms`
|
||||
- `target.pull`
|
||||
- `target.tags`
|
||||
- `target.target`
|
||||
|
||||
For example, if `compose.yaml` and `docker-bake.hcl` both define the `tags`
|
||||
attribute, the `docker-bake.hcl` is used.
|
||||
|
||||
```console
|
||||
$ cat compose.yaml
|
||||
services:
|
||||
webapp:
|
||||
build:
|
||||
context: .
|
||||
tags:
|
||||
- bar
|
||||
$ cat docker-bake.hcl
|
||||
target "webapp" {
|
||||
tags = ["foo"]
|
||||
}
|
||||
$ docker buildx bake --print webapp
|
||||
{
|
||||
"group": {
|
||||
"default": {
|
||||
"targets": [
|
||||
"webapp"
|
||||
]
|
||||
}
|
||||
},
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"tags": [
|
||||
"foo"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All other attributes are merged. For example, if `compose.yaml` and
|
||||
`docker-bake.hcl` both define unique entries for the `labels` attribute, all
|
||||
entries are included. Duplicate entries for the same label are overridden.
|
||||
|
||||
```console
|
||||
$ cat compose.yaml
|
||||
services:
|
||||
webapp:
|
||||
build:
|
||||
context: .
|
||||
labels:
|
||||
com.example.foo: "foo"
|
||||
com.example.name: "Alice"
|
||||
$ cat docker-bake.hcl
|
||||
target "webapp" {
|
||||
labels = {
|
||||
"com.example.bar" = "bar"
|
||||
"com.example.name" = "Bob"
|
||||
}
|
||||
}
|
||||
$ docker buildx bake --print webapp
|
||||
{
|
||||
"group": {
|
||||
"default": {
|
||||
"targets": [
|
||||
"webapp"
|
||||
]
|
||||
}
|
||||
},
|
||||
"target": {
|
||||
"webapp": {
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"labels": {
|
||||
"com.example.foo": "foo",
|
||||
"com.example.bar": "bar",
|
||||
"com.example.name": "Bob"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Syntax
|
||||
@@ -113,8 +213,9 @@ target "webapp" {
|
||||
The following table shows the complete list of attributes that you can assign to a target:
|
||||
|
||||
| Name | Type | Description |
|
||||
| ----------------------------------------------- | ------- | -------------------------------------------------------------------- |
|
||||
|-------------------------------------------------|---------|----------------------------------------------------------------------|
|
||||
| [`args`](#targetargs) | Map | Build arguments |
|
||||
| [`annotations`](#targetannotations) | List | Exporter annotations |
|
||||
| [`attest`](#targetattest) | List | Build attestations |
|
||||
| [`cache-from`](#targetcache-from) | List | External cache sources |
|
||||
| [`cache-to`](#targetcache-to) | List | External cache destinations |
|
||||
@@ -132,9 +233,11 @@ The following table shows the complete list of attributes that you can assign to
|
||||
| [`platforms`](#targetplatforms) | List | Target platforms |
|
||||
| [`pull`](#targetpull) | Boolean | Always pull images |
|
||||
| [`secret`](#targetsecret) | List | Secrets to expose to the build |
|
||||
| [`shm-size`](#targetshm-size) | List | Size of `/dev/shm` |
|
||||
| [`ssh`](#targetssh) | List | SSH agent sockets or keys to expose to the build |
|
||||
| [`tags`](#targettags) | List | Image names and tags |
|
||||
| [`target`](#targettarget) | String | Target build stage |
|
||||
| [`ulimits`](#targetulimits) | List | Ulimit options |
|
||||
|
||||
### `target.args`
|
||||
|
||||
@@ -171,6 +274,41 @@ target "db" {
|
||||
}
|
||||
```
|
||||
|
||||
### `target.annotations`
|
||||
|
||||
The `annotations` attribute lets you add annotations to images built with bake.
|
||||
The key takes a list of annotations, in the format of `KEY=VALUE`.
|
||||
|
||||
```hcl
|
||||
target "default" {
|
||||
output = ["type=image,name=foo"]
|
||||
annotations = ["org.opencontainers.image.authors=dvdksn"]
|
||||
}
|
||||
```
|
||||
|
||||
is the same as
|
||||
|
||||
```hcl
|
||||
target "default" {
|
||||
output = ["type=image,name=foo,annotation.org.opencontainers.image.authors=dvdksn"]
|
||||
}
|
||||
```
|
||||
|
||||
By default, the annotation is added to image manifests. You can configure the
|
||||
level of the annotations by adding a prefix to the annotation, containing a
|
||||
comma-separated list of all the levels that you want to annotate. The following
|
||||
example adds annotations to both the image index and manifests.
|
||||
|
||||
```hcl
|
||||
target "default" {
|
||||
output = ["type=image,name=foo"]
|
||||
annotations = ["index,manifest:org.opencontainers.image.authors=dvdksn"]
|
||||
}
|
||||
```
|
||||
|
||||
Read about the supported levels in
|
||||
[Specifying annotation levels](https://docs.docker.com/build/building/annotations/#specifying-annotation-levels).
|
||||
|
||||
### `target.attest`
|
||||
|
||||
The `attest` attribute lets you apply [build attestations][attestations] to the target.
|
||||
@@ -696,6 +834,29 @@ RUN --mount=type=secret,id=KUBECONFIG \
|
||||
KUBECONFIG=$(cat /run/secrets/KUBECONFIG) helm upgrade --install
|
||||
```
|
||||
|
||||
### `target.shm-size`
|
||||
|
||||
Sets the size of the shared memory allocated for build containers when using
|
||||
`RUN` instructions.
|
||||
|
||||
The format is `<number><unit>`. `number` must be greater than `0`. Unit is
|
||||
optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g`
|
||||
(gigabytes). If you omit the unit, the system uses bytes.
|
||||
|
||||
This is the same as the `--shm-size` flag for `docker build`.
|
||||
|
||||
```hcl
|
||||
target "default" {
|
||||
shm-size = "128m"
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> In most cases, it is recommended to let the builder automatically determine
|
||||
> the appropriate configurations. Manual adjustments should only be considered
|
||||
> when specific performance tuning is required for complex build scenarios.
|
||||
|
||||
### `target.ssh`
|
||||
|
||||
Defines SSH agent sockets or keys to expose to the build.
|
||||
@@ -742,6 +903,32 @@ target "default" {
|
||||
}
|
||||
```
|
||||
|
||||
### `target.ulimits`
|
||||
|
||||
Ulimits overrides the default ulimits of build's containers when using `RUN`
|
||||
instructions and are specified with a soft and hard limit as such:
|
||||
`<type>=<soft limit>[:<hard limit>]`, for example:
|
||||
|
||||
```hcl
|
||||
target "app" {
|
||||
ulimits = [
|
||||
"nofile=1024:1024"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> If you do not provide a `hard limit`, the `soft limit` is used
|
||||
> for both values. If no `ulimits` are set, they are inherited from
|
||||
> the default `ulimits` set on the daemon.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> In most cases, it is recommended to let the builder automatically determine
|
||||
> the appropriate configurations. Manual adjustments should only be considered
|
||||
> when specific performance tuning is required for complex build scenarios.
|
||||
|
||||
## Group
|
||||
|
||||
Groups allow you to invoke multiple builds (targets) at once.
|
||||
@@ -933,20 +1120,20 @@ target "webapp-dev" {
|
||||
|
||||
[attestations]: https://docs.docker.com/build/attestations/
|
||||
[bake_stdlib]: https://github.com/docker/buildx/blob/master/bake/hclparser/stdlib.go
|
||||
[build-arg]: https://docs.docker.com/engine/reference/commandline/build/#build-arg
|
||||
[build-context]: https://docs.docker.com/engine/reference/commandline/buildx_build/#build-context
|
||||
[build-arg]: https://docs.docker.com/reference/cli/docker/image/build/#build-arg
|
||||
[build-context]: https://docs.docker.com/reference/cli/docker/buildx/build/#build-context
|
||||
[cache-backends]: https://docs.docker.com/build/cache/backends/
|
||||
[cache-from]: https://docs.docker.com/engine/reference/commandline/buildx_build/#cache-from
|
||||
[cache-to]: https://docs.docker.com/engine/reference/commandline/buildx_build/#cache-to
|
||||
[context]: https://docs.docker.com/engine/reference/commandline/buildx_build/#build-context
|
||||
[file]: https://docs.docker.com/engine/reference/commandline/build/#file
|
||||
[cache-from]: https://docs.docker.com/reference/cli/docker/buildx/build/#cache-from
|
||||
[cache-to]: https://docs.docker.com/reference/cli/docker/buildx/build/#cache-to
|
||||
[context]: https://docs.docker.com/reference/cli/docker/buildx/build/#build-context
|
||||
[file]: https://docs.docker.com/reference/cli/docker/image/build/#file
|
||||
[go-cty]: https://github.com/zclconf/go-cty/tree/main/cty/function/stdlib
|
||||
[hcl-funcs]: https://docs.docker.com/build/bake/hcl-funcs/
|
||||
[output]: https://docs.docker.com/engine/reference/commandline/buildx_build/#output
|
||||
[platform]: https://docs.docker.com/engine/reference/commandline/buildx_build/#platform
|
||||
[run_mount_secret]: https://docs.docker.com/engine/reference/builder/#run---mounttypesecret
|
||||
[secret]: https://docs.docker.com/engine/reference/commandline/buildx_build/#secret
|
||||
[ssh]: https://docs.docker.com/engine/reference/commandline/buildx_build/#ssh
|
||||
[tag]: https://docs.docker.com/engine/reference/commandline/build/#tag
|
||||
[target]: https://docs.docker.com/engine/reference/commandline/build/#target
|
||||
[output]: https://docs.docker.com/reference/cli/docker/buildx/build/#output
|
||||
[platform]: https://docs.docker.com/reference/cli/docker/buildx/build/#platform
|
||||
[run_mount_secret]: https://docs.docker.com/reference/dockerfile/#run---mounttypesecret
|
||||
[secret]: https://docs.docker.com/reference/cli/docker/buildx/build/#secret
|
||||
[ssh]: https://docs.docker.com/reference/cli/docker/buildx/build/#ssh
|
||||
[tag]: https://docs.docker.com/reference/cli/docker/image/build/#tag
|
||||
[target]: https://docs.docker.com/reference/cli/docker/image/build/#target
|
||||
[userfunc]: https://github.com/hashicorp/hcl/tree/main/ext/userfunc
|
||||
|
||||
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/commands"
|
||||
clidocstool "github.com/docker/cli-docs-tool"
|
||||
@@ -26,6 +27,28 @@ type options struct {
|
||||
formats []string
|
||||
}
|
||||
|
||||
// fixUpExperimentalCLI trims the " (EXPERIMENTAL)" suffix from the CLI output,
|
||||
// as docs.docker.com already displays "experimental (CLI)",
|
||||
//
|
||||
// https://github.com/docker/buildx/pull/2188#issuecomment-1889487022
|
||||
func fixUpExperimentalCLI(cmd *cobra.Command) {
|
||||
const (
|
||||
annotationExperimentalCLI = "experimentalCLI"
|
||||
suffixExperimental = " (EXPERIMENTAL)"
|
||||
)
|
||||
if _, ok := cmd.Annotations[annotationExperimentalCLI]; ok {
|
||||
cmd.Short = strings.TrimSuffix(cmd.Short, suffixExperimental)
|
||||
}
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if _, ok := f.Annotations[annotationExperimentalCLI]; ok {
|
||||
f.Usage = strings.TrimSuffix(f.Usage, suffixExperimental)
|
||||
}
|
||||
})
|
||||
for _, c := range cmd.Commands() {
|
||||
fixUpExperimentalCLI(c)
|
||||
}
|
||||
}
|
||||
|
||||
func gen(opts *options) error {
|
||||
log.SetFlags(0)
|
||||
|
||||
@@ -57,6 +80,8 @@ func gen(opts *options) error {
|
||||
return err
|
||||
}
|
||||
case "yaml":
|
||||
// fix up is needed only for yaml (used for generating docs.docker.com contents)
|
||||
fixUpExperimentalCLI(cmd)
|
||||
if err = c.GenYamlTree(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -19,11 +19,13 @@ your environment.
|
||||
$ export BUILDX_EXPERIMENTAL=1
|
||||
```
|
||||
|
||||
To start a debug session for a build, you can use the `--invoke` flag with the
|
||||
build command to specify a command to launch in the resulting image.
|
||||
To start a debug session for a build, you can use the `buildx debug` command with `--invoke` flag to specify a command to launch in the resulting image.
|
||||
`buildx debug` command provides `buildx debug build` subcommand that provides the same features as the normal `buildx build` command but allows launching the debugger session after the build.
|
||||
|
||||
Arguments available after `buildx debug build` are the same as the normal `buildx build`.
|
||||
|
||||
```console
|
||||
$ docker buildx build --invoke /bin/sh .
|
||||
$ docker buildx debug --invoke /bin/sh build .
|
||||
[+] Building 4.2s (19/19) FINISHED
|
||||
=> [internal] connecting to local controller 0.0s
|
||||
=> [internal] load build definition from Dockerfile 0.0s
|
||||
@@ -49,13 +51,23 @@ bin etc lib mnt proc run srv tmp var
|
||||
dev home media opt root sbin sys usr work
|
||||
```
|
||||
|
||||
#### `on-error`
|
||||
Optional long form allows you specifying detailed configurations of the process.
|
||||
It must be CSV-styled comma-separated key-value pairs.
|
||||
Supported keys are `args` (can be JSON array format), `entrypoint` (can be JSON array format), `env` (can be JSON array format), `user`, `cwd` and `tty` (bool).
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
$ docker buildx debug --invoke 'entrypoint=["sh"],"args=[""-c"", ""env | grep -e FOO -e AAA""]","env=[""FOO=bar"", ""AAA=bbb""]"' build .
|
||||
```
|
||||
|
||||
#### `on` flag
|
||||
|
||||
If you want to start a debug session when a build fails, you can use
|
||||
`--invoke=on-error` to start a debug session when the build fails.
|
||||
`--on=error` to start a debug session when the build fails.
|
||||
|
||||
```console
|
||||
$ docker buildx build --invoke on-error .
|
||||
$ docker buildx debug --on=error build .
|
||||
[+] Building 4.2s (19/19) FINISHED
|
||||
=> [internal] connecting to local controller 0.0s
|
||||
=> [internal] load build definition from Dockerfile 0.0s
|
||||
@@ -75,13 +87,13 @@ Interactive container was restarted with process "edmzor60nrag7rh1mbi4o9lm8". Pr
|
||||
|
||||
This allows you to explore the state of the image when the build failed.
|
||||
|
||||
#### `debug-shell`
|
||||
#### Launch the debug session directly with `buildx debug` subcommand
|
||||
|
||||
If you want to drop into a debug session without first starting the build, you
|
||||
can use `--invoke=debug-shell` to start a debug session.
|
||||
can use `buildx debug` command to start a debug session.
|
||||
|
||||
```
|
||||
$ docker buildx build --invoke debug-shell .
|
||||
$ docker buildx debug
|
||||
[+] Building 4.2s (19/19) FINISHED
|
||||
=> [internal] connecting to local controller 0.0s
|
||||
(buildx)
|
||||
@@ -106,12 +118,12 @@ Available commands are:
|
||||
disconnect disconnect a client from a buildx server. Specific session ID can be specified an arg
|
||||
exec execute a process in the interactive container
|
||||
exit exits monitor
|
||||
help shows this message
|
||||
help shows this message. Optionally pass a command name as an argument to print the detailed usage.
|
||||
kill kill buildx server
|
||||
list list buildx sessions
|
||||
ps list processes invoked by "exec". Use "attach" to attach IO to that process
|
||||
reload reloads the context and build it
|
||||
rollback re-runs the interactive container with initial rootfs contents
|
||||
rollback re-runs the interactive container with the step's rootfs contents
|
||||
```
|
||||
|
||||
## Build controllers
|
||||
@@ -125,15 +137,15 @@ To detach the build process from the CLI, you can use the `--detach=true` flag w
|
||||
the build command.
|
||||
|
||||
```console
|
||||
$ docker buildx build --detach=true --invoke /bin/sh .
|
||||
$ docker buildx debug --invoke /bin/sh build --detach=true .
|
||||
```
|
||||
|
||||
If you start a debugging session using the `--invoke` flag with a detached
|
||||
build, then you can attach to it using the `buildx debug-shell` subcommand to
|
||||
build, then you can attach to it using the `buildx debug` command to
|
||||
immediately enter the monitor mode.
|
||||
|
||||
```console
|
||||
$ docker buildx debug-shell
|
||||
$ docker buildx debug
|
||||
[+] Building 0.0s (1/1) FINISHED
|
||||
=> [internal] connecting to remote controller
|
||||
(buildx) list
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# buildx
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx [OPTIONS] COMMAND
|
||||
```
|
||||
|
||||
@@ -9,24 +9,22 @@ Extended build capabilities with BuildKit
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:-----------------------------------------------|:-------------------------------------------|
|
||||
| [`_INTERNAL_SERVE`](buildx__INTERNAL_SERVE.md) | |
|
||||
| [`bake`](buildx_bake.md) | Build from a file |
|
||||
| [`build`](buildx_build.md) | Start a build |
|
||||
| [`create`](buildx_create.md) | Create a new builder instance |
|
||||
| [`debug-shell`](buildx_debug-shell.md) | Start a monitor |
|
||||
| [`du`](buildx_du.md) | Disk usage |
|
||||
| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry |
|
||||
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
||||
| [`install`](buildx_install.md) | Install buildx as a 'docker builder' alias |
|
||||
| [`ls`](buildx_ls.md) | List builder instances |
|
||||
| [`prune`](buildx_prune.md) | Remove build cache |
|
||||
| [`rm`](buildx_rm.md) | Remove a builder instance |
|
||||
| [`stop`](buildx_stop.md) | Stop builder instance |
|
||||
| [`uninstall`](buildx_uninstall.md) | Uninstall the 'docker builder' alias |
|
||||
| [`use`](buildx_use.md) | Set the current builder instance |
|
||||
| [`version`](buildx_version.md) | Show buildx version information |
|
||||
| Name | Description |
|
||||
|:-------------------------------------|:------------------------------------------------|
|
||||
| [`bake`](buildx_bake.md) | Build from a file |
|
||||
| [`build`](buildx_build.md) | Start a build |
|
||||
| [`create`](buildx_create.md) | Create a new builder instance |
|
||||
| [`debug`](buildx_debug.md) | Start debugger (EXPERIMENTAL) |
|
||||
| [`dial-stdio`](buildx_dial-stdio.md) | Proxy current stdio streams to builder instance |
|
||||
| [`du`](buildx_du.md) | Disk usage |
|
||||
| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry |
|
||||
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
||||
| [`ls`](buildx_ls.md) | List builder instances |
|
||||
| [`prune`](buildx_prune.md) | Remove build cache |
|
||||
| [`rm`](buildx_rm.md) | Remove one or more builder instances |
|
||||
| [`stop`](buildx_stop.md) | Stop builder instance |
|
||||
| [`use`](buildx_use.md) | Set the current builder instance |
|
||||
| [`version`](buildx_version.md) | Show buildx version information |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# buildx bake
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx bake [OPTIONS] [TARGET...]
|
||||
```
|
||||
|
||||
@@ -13,27 +13,27 @@ Build from a file
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------|
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Build definition file |
|
||||
| `--load` | | | Shorthand for `--set=*.output=type=docker` |
|
||||
| `--metadata-file` | `string` | | Write build result metadata to the file |
|
||||
| [`--no-cache`](#no-cache) | | | Do not use cache when building the image |
|
||||
| [`--print`](#print) | | | Print the options without building |
|
||||
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||
| [`--provenance`](#provenance) | `string` | | Shorthand for `--set=*.attest=type=provenance` |
|
||||
| [`--pull`](#pull) | | | Always attempt to pull all referenced images |
|
||||
| `--push` | | | Shorthand for `--set=*.output=type=registry` |
|
||||
| [`--sbom`](#sbom) | `string` | | Shorthand for `--set=*.attest=type=sbom` |
|
||||
| [`--set`](#set) | `stringArray` | | Override target value (e.g., `targetpattern.key=value`) |
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------|
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Build definition file |
|
||||
| `--load` | | | Shorthand for `--set=*.output=type=docker` |
|
||||
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to a file |
|
||||
| [`--no-cache`](#no-cache) | | | Do not use cache when building the image |
|
||||
| [`--print`](#print) | | | Print the options without building |
|
||||
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||
| [`--provenance`](#provenance) | `string` | | Shorthand for `--set=*.attest=type=provenance` |
|
||||
| [`--pull`](#pull) | | | Always attempt to pull all referenced images |
|
||||
| `--push` | | | Shorthand for `--set=*.output=type=registry` |
|
||||
| [`--sbom`](#sbom) | `string` | | Shorthand for `--set=*.attest=type=sbom` |
|
||||
| [`--set`](#set) | `stringArray` | | Override target value (e.g., `targetpattern.key=value`) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
Bake is a high-level build command. Each specified target will run in parallel
|
||||
Bake is a high-level build command. Each specified target runs in parallel
|
||||
as part of the build.
|
||||
|
||||
Read [High-level build options with Bake](https://docs.docker.com/build/bake/)
|
||||
@@ -54,8 +54,8 @@ Same as [`buildx --builder`](buildx.md#builder).
|
||||
### <a name="file"></a> Specify a build definition file (-f, --file)
|
||||
|
||||
Use the `-f` / `--file` option to specify the build definition file to use.
|
||||
The file can be an HCL, JSON or Compose file. If multiple files are specified
|
||||
they are all read and configurations are combined.
|
||||
The file can be an HCL, JSON or Compose file. If multiple files are specified,
|
||||
all are read and the build configurations are combined.
|
||||
|
||||
You can pass the names of the targets to build, to build only specific target(s).
|
||||
The following example builds the `db` and `webapp-release` targets that are
|
||||
@@ -90,9 +90,80 @@ $ docker buildx bake -f docker-bake.dev.hcl db webapp-release
|
||||
See the [Bake file reference](https://docs.docker.com/build/bake/reference/)
|
||||
for more details.
|
||||
|
||||
### <a name="no-cache"></a> Do not use cache when building the image (--no-cache)
|
||||
### <a name="metadata-file"></a> Write build results metadata to a file (--metadata-file)
|
||||
|
||||
Same as `build --no-cache`. Do not use cache when building the image.
|
||||
Similar to [`buildx build --metadata-file`](buildx_build.md#metadata-file) but
|
||||
writes a map of results for each target such as:
|
||||
|
||||
```hcl
|
||||
# docker-bake.hcl
|
||||
group "default" {
|
||||
targets = ["db", "webapp-dev"]
|
||||
}
|
||||
|
||||
target "db" {
|
||||
dockerfile = "Dockerfile.db"
|
||||
tags = ["docker.io/username/db"]
|
||||
}
|
||||
|
||||
target "webapp-dev" {
|
||||
dockerfile = "Dockerfile.webapp"
|
||||
tags = ["docker.io/username/webapp"]
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
$ docker buildx bake --load --metadata-file metadata.json .
|
||||
$ cat metadata.json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"db": {
|
||||
"buildx.build.provenance": {},
|
||||
"buildx.build.ref": "mybuilder/mybuilder0/0fjb6ubs52xx3vygf6fgdl611",
|
||||
"containerimage.config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
|
||||
"containerimage.descriptor": {
|
||||
"annotations": {
|
||||
"config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
|
||||
"org.opencontainers.image.created": "2022-02-08T21:28:03Z"
|
||||
},
|
||||
"digest": "sha256:19ffeab6f8bc9293ac2c3fdf94ebe28396254c993aea0b5a542cfb02e0883fa3",
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"size": 506
|
||||
},
|
||||
"containerimage.digest": "sha256:19ffeab6f8bc9293ac2c3fdf94ebe28396254c993aea0b5a542cfb02e0883fa3"
|
||||
},
|
||||
"webapp-dev": {
|
||||
"buildx.build.provenance": {},
|
||||
"buildx.build.ref": "mybuilder/mybuilder0/kamngmcgyzebqxwu98b4lfv3n",
|
||||
"containerimage.config.digest": "sha256:9651cc2b3c508f697c9c43b67b64c8359c2865c019e680aac1c11f4b875b67e0",
|
||||
"containerimage.descriptor": {
|
||||
"annotations": {
|
||||
"config.digest": "sha256:9651cc2b3c508f697c9c43b67b64c8359c2865c019e680aac1c11f4b875b67e0",
|
||||
"org.opencontainers.image.created": "2022-02-08T21:28:15Z"
|
||||
},
|
||||
"digest": "sha256:6d9ac9237a84afe1516540f40a0fafdc86859b2141954b4d643af7066d598b74",
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"size": 506
|
||||
},
|
||||
"containerimage.digest": "sha256:6d9ac9237a84afe1516540f40a0fafdc86859b2141954b4d643af7066d598b74"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Build record [provenance](https://docs.docker.com/build/attestations/slsa-provenance/#provenance-attestation-example)
|
||||
> (`buildx.build.provenance`) includes minimal provenance by default. Set the
|
||||
> `BUILDX_METADATA_PROVENANCE` environment variable to customize this behavior:
|
||||
> * `min` sets minimal provenance (default).
|
||||
> * `max` sets full provenance.
|
||||
> * `disabled`, `false` or `0` does not set any provenance.
|
||||
|
||||
### <a name="no-cache"></a> Don't use cache when building the image (--no-cache)
|
||||
|
||||
Same as `build --no-cache`. Don't use cache when building the image.
|
||||
|
||||
### <a name="print"></a> Print the options without building (--print)
|
||||
|
||||
@@ -154,7 +225,7 @@ $ docker buildx bake --set *.platform=linux/arm64 # overrides platform for a
|
||||
$ docker buildx bake --set foo*.no-cache # bypass caching only for targets starting with 'foo'
|
||||
```
|
||||
|
||||
Complete list of overridable fields:
|
||||
You can override the following fields:
|
||||
|
||||
* `args`
|
||||
* `cache-from`
|
||||
@@ -162,6 +233,7 @@ Complete list of overridable fields:
|
||||
* `context`
|
||||
* `dockerfile`
|
||||
* `labels`
|
||||
* `load`
|
||||
* `no-cache`
|
||||
* `no-cache-filter`
|
||||
* `output`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# buildx build
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx build [OPTIONS] PATH | URL | -
|
||||
```
|
||||
|
||||
@@ -13,44 +13,44 @@ Start a build
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------|:----------|:----------------------------------------------------------------------------------------------------|
|
||||
| [`--add-host`](https://docs.docker.com/engine/reference/commandline/build/#add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
||||
| [`--allow`](#allow) | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
|
||||
| [`--attest`](#attest) | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
||||
| [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
||||
| [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| [`--cache-from`](#cache-from) | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
||||
| [`--cache-to`](#cache-to) | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
||||
| [`--cgroup-parent`](https://docs.docker.com/engine/reference/commandline/build/#cgroup-parent) | `string` | | Optional parent cgroup for the container |
|
||||
| `--detach` | | | Detach buildx server (supported only on linux) [experimental] |
|
||||
| [`-f`](https://docs.docker.com/engine/reference/commandline/build/#file), [`--file`](https://docs.docker.com/engine/reference/commandline/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
||||
| `--iidfile` | `string` | | Write the image ID to the file |
|
||||
| `--invoke` | `string` | | Invoke a command after the build [experimental] |
|
||||
| `--label` | `stringArray` | | Set metadata for an image |
|
||||
| [`--load`](#load) | | | Shorthand for `--output=type=docker` |
|
||||
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to the file |
|
||||
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
||||
| `--no-cache` | | | Do not use cache when building the image |
|
||||
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
||||
| [`-o`](#output), [`--output`](#output) | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
||||
| [`--platform`](#platform) | `stringArray` | | Set target platform for build |
|
||||
| `--print` | `string` | | Print result of information request (e.g., outline, targets) [experimental] |
|
||||
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||
| [`--provenance`](#provenance) | `string` | | Shorthand for `--attest=type=provenance` |
|
||||
| `--pull` | | | Always attempt to pull all referenced images |
|
||||
| [`--push`](#push) | | | Shorthand for `--output=type=registry` |
|
||||
| `-q`, `--quiet` | | | Suppress the build output and print image ID on success |
|
||||
| `--root` | `string` | | Specify root directory of server to connect [experimental] |
|
||||
| [`--sbom`](#sbom) | `string` | | Shorthand for `--attest=type=sbom` |
|
||||
| [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||
| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) [experimental] |
|
||||
| [`--shm-size`](#shm-size) | `bytes` | `0` | Size of `/dev/shm` |
|
||||
| [`--ssh`](#ssh) | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||
| [`-t`](https://docs.docker.com/engine/reference/commandline/build/#tag), [`--tag`](https://docs.docker.com/engine/reference/commandline/build/#tag) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
||||
| [`--target`](https://docs.docker.com/engine/reference/commandline/build/#target) | `string` | | Set the target build stage to build |
|
||||
| [`--ulimit`](#ulimit) | `ulimit` | | Ulimit options |
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------------------------------------------------------------------------------------------------------------------|:--------------|:----------|:----------------------------------------------------------------------------------------------------|
|
||||
| [`--add-host`](https://docs.docker.com/reference/cli/docker/image/build/#add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
||||
| [`--allow`](#allow) | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
|
||||
| [`--annotation`](#annotation) | `stringArray` | | Add annotation to the image |
|
||||
| [`--attest`](#attest) | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
||||
| [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
||||
| [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| [`--cache-from`](#cache-from) | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
||||
| [`--cache-to`](#cache-to) | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
||||
| [`--cgroup-parent`](https://docs.docker.com/reference/cli/docker/image/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
||||
| `--detach` | | | Detach buildx server (supported only on linux) (EXPERIMENTAL) |
|
||||
| [`-f`](https://docs.docker.com/reference/cli/docker/image/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/image/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
||||
| `--iidfile` | `string` | | Write the image ID to a file |
|
||||
| `--label` | `stringArray` | | Set metadata for an image |
|
||||
| [`--load`](#load) | | | Shorthand for `--output=type=docker` |
|
||||
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to a file |
|
||||
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
||||
| `--no-cache` | | | Do not use cache when building the image |
|
||||
| [`--no-cache-filter`](#no-cache-filter) | `stringArray` | | Do not cache specified stages |
|
||||
| [`-o`](#output), [`--output`](#output) | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
||||
| [`--platform`](#platform) | `stringArray` | | Set target platform for build |
|
||||
| `--print` | `string` | | Print result of information request (e.g., outline, targets) (EXPERIMENTAL) |
|
||||
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||
| [`--provenance`](#provenance) | `string` | | Shorthand for `--attest=type=provenance` |
|
||||
| `--pull` | | | Always attempt to pull all referenced images |
|
||||
| [`--push`](#push) | | | Shorthand for `--output=type=registry` |
|
||||
| `-q`, `--quiet` | | | Suppress the build output and print image ID on success |
|
||||
| `--root` | `string` | | Specify root directory of server to connect (EXPERIMENTAL) |
|
||||
| [`--sbom`](#sbom) | `string` | | Shorthand for `--attest=type=sbom` |
|
||||
| [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||
| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) (EXPERIMENTAL) |
|
||||
| [`--shm-size`](#shm-size) | `bytes` | `0` | Shared memory size for build containers |
|
||||
| [`--ssh`](#ssh) | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||
| [`-t`](https://docs.docker.com/reference/cli/docker/image/build/#tag), [`--tag`](https://docs.docker.com/reference/cli/docker/image/build/#tag) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
||||
| [`--target`](https://docs.docker.com/reference/cli/docker/image/build/#target) | `string` | | Set the target build stage to build |
|
||||
| [`--ulimit`](#ulimit) | `ulimit` | | Ulimit options |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -64,14 +64,60 @@ The `buildx build` command starts a build using BuildKit. This command is simila
|
||||
to the UI of `docker build` command and takes the same flags and arguments.
|
||||
|
||||
For documentation on most of these flags, refer to the [`docker build`
|
||||
documentation](https://docs.docker.com/engine/reference/commandline/build/). In
|
||||
here we'll document a subset of the new flags.
|
||||
documentation](https://docs.docker.com/reference/cli/docker/image/build/).
|
||||
This page describes a subset of the new flags.
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="annotation"></a> Create annotations (--annotation)
|
||||
|
||||
```text
|
||||
--annotation="key=value"
|
||||
--annotation="[type:]key=value"
|
||||
```
|
||||
|
||||
Add OCI annotations to the image index, manifest, or descriptor.
|
||||
The following example adds the `foo=bar` annotation to the image manifests:
|
||||
|
||||
```console
|
||||
$ docker buildx build -t TAG --annotation "foo=bar" --push .
|
||||
```
|
||||
|
||||
You can optionally add a type prefix to specify the level of the annotation. By
|
||||
default, the image manifest is annotated. The following example adds the
|
||||
`foo=bar` annotation the image index instead of the manifests:
|
||||
|
||||
```console
|
||||
$ docker buildx build -t TAG --annotation "index:foo=bar" --push .
|
||||
```
|
||||
|
||||
You can specify multiple types, separated by a comma (,) to add the annotation
|
||||
to multiple image components. The following example adds the `foo=bar`
|
||||
annotation to image index, descriptors, manifests:
|
||||
|
||||
```console
|
||||
$ docker buildx build -t TAG --annotation "index,manifest,manifest-descriptor:foo=bar" --push .
|
||||
```
|
||||
|
||||
You can also specify a platform qualifier in square brackets (`[os/arch]`) in
|
||||
the type prefix, to apply the annotation to a subset of manifests with the
|
||||
matching platform. The following example adds the `foo=bar` annotation only to
|
||||
the manifest with the `linux/amd64` platform:
|
||||
|
||||
```console
|
||||
$ docker buildx build -t TAG --annotation "manifest[linux/amd64]:foo=bar" --push .
|
||||
```
|
||||
|
||||
Wildcards are not supported in the platform qualifier; you can't specify a type
|
||||
prefix like `manifest[linux/*]` to add annotations only to manifests which has
|
||||
`linux` as the OS platform.
|
||||
|
||||
For more information about annotations, see
|
||||
[Annotations](https://docs.docker.com/build/building/annotations/).
|
||||
|
||||
### <a name="attest"></a> Create attestations (--attest)
|
||||
|
||||
```
|
||||
```text
|
||||
--attest=type=sbom,...
|
||||
--attest=type=provenance,...
|
||||
```
|
||||
@@ -98,7 +144,7 @@ BuildKit currently supports:
|
||||
|
||||
### <a name="allow"></a> Allow extra privileged entitlement (--allow)
|
||||
|
||||
```
|
||||
```text
|
||||
--allow=ENTITLEMENT
|
||||
```
|
||||
|
||||
@@ -106,12 +152,10 @@ Allow extra privileged entitlement. List of entitlements:
|
||||
|
||||
- `network.host` - Allows executions with host networking.
|
||||
- `security.insecure` - Allows executions without sandbox. See
|
||||
[related Dockerfile extensions](https://docs.docker.com/engine/reference/builder/#run---securitysandbox).
|
||||
[related Dockerfile extensions](https://docs.docker.com/reference/dockerfile/#run---security).
|
||||
|
||||
For entitlements to be enabled, the `buildkitd` daemon also needs to allow them
|
||||
with `--allow-insecure-entitlement` (see [`create --buildkitd-flags`](buildx_create.md#buildkitd-flags))
|
||||
|
||||
**Examples**
|
||||
For entitlements to be enabled, the BuildKit daemon also needs to allow them
|
||||
with `--allow-insecure-entitlement` (see [`create --buildkitd-flags`](buildx_create.md#buildkitd-flags)).
|
||||
|
||||
```console
|
||||
$ docker buildx create --use --name insecure-builder --buildkitd-flags '--allow-insecure-entitlement security.insecure'
|
||||
@@ -120,26 +164,23 @@ $ docker buildx build --allow security.insecure .
|
||||
|
||||
### <a name="build-arg"></a> Set build-time variables (--build-arg)
|
||||
|
||||
Same as [`docker build` command](https://docs.docker.com/engine/reference/commandline/build/#build-arg).
|
||||
Same as [`docker build` command](https://docs.docker.com/reference/cli/docker/image/build/#build-arg).
|
||||
|
||||
There are also useful built-in build args like:
|
||||
There are also useful built-in build arguments, such as:
|
||||
|
||||
* `BUILDKIT_CONTEXT_KEEP_GIT_DIR=<bool>` trigger git context to keep the `.git` directory
|
||||
* `BUILDKIT_INLINE_BUILDINFO_ATTRS=<bool>` inline build info attributes in image config or not
|
||||
* `BUILDKIT_INLINE_CACHE=<bool>` inline cache metadata to image config or not
|
||||
* `BUILDKIT_MULTI_PLATFORM=<bool>` opt into deterministic output regardless of multi-platform output or not
|
||||
* `BUILDKIT_CONTEXT_KEEP_GIT_DIR=<bool>`: trigger git context to keep the `.git` directory
|
||||
* `BUILDKIT_INLINE_CACHE=<bool>`: inline cache metadata to image config or not
|
||||
* `BUILDKIT_MULTI_PLATFORM=<bool>`: opt into deterministic output regardless of multi-platform output or not
|
||||
|
||||
```console
|
||||
$ docker buildx build --build-arg BUILDKIT_MULTI_PLATFORM=1 .
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> More built-in build args can be found in [Dockerfile reference docs](https://docs.docker.com/engine/reference/builder/#buildkit-built-in-build-args).
|
||||
Learn more about the built-in build arguments in the [Dockerfile reference docs](https://docs.docker.com/reference/dockerfile/#buildkit-built-in-build-args).
|
||||
|
||||
### <a name="build-context"></a> Additional build contexts (--build-context)
|
||||
|
||||
```
|
||||
```text
|
||||
--build-context=name=VALUE
|
||||
```
|
||||
|
||||
@@ -167,7 +208,7 @@ FROM alpine
|
||||
COPY --from=project myfile /
|
||||
```
|
||||
|
||||
#### <a name="source-oci-layout"></a> Source image from OCI layout directory
|
||||
#### <a name="source-oci-layout"></a> Use an OCI layout directory as build context
|
||||
|
||||
Source an image from a local [OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md),
|
||||
either by tag, or by digest:
|
||||
@@ -195,7 +236,7 @@ Same as [`buildx --builder`](buildx.md#builder).
|
||||
|
||||
### <a name="cache-from"></a> Use an external cache source for a build (--cache-from)
|
||||
|
||||
```
|
||||
```text
|
||||
--cache-from=[NAME|type=TYPE[,KEY=VALUE]]
|
||||
```
|
||||
|
||||
@@ -231,7 +272,7 @@ More info about cache exporters and available attributes: https://github.com/mob
|
||||
|
||||
### <a name="cache-to"></a> Export build cache to an external cache destination (--cache-to)
|
||||
|
||||
```
|
||||
```text
|
||||
--cache-to=[NAME|type=TYPE[,KEY=VALUE]]
|
||||
```
|
||||
|
||||
@@ -248,9 +289,8 @@ Export build cache to an external cache destination. Supported types are
|
||||
- [`s3` type](https://github.com/moby/buildkit#s3-cache-experimental) exports
|
||||
cache to a S3 bucket.
|
||||
|
||||
`docker` driver currently only supports exporting inline cache metadata to image
|
||||
configuration. Alternatively, `--build-arg BUILDKIT_INLINE_CACHE=1` can be used
|
||||
to trigger inline cache exporter.
|
||||
The `docker` driver only supports cache exports using the `inline` and `local`
|
||||
cache backends.
|
||||
|
||||
Attribute key:
|
||||
|
||||
@@ -274,7 +314,7 @@ More info about cache exporters and available attributes: https://github.com/mob
|
||||
Shorthand for [`--output=type=docker`](#docker). Will automatically load the
|
||||
single-platform build result to `docker images`.
|
||||
|
||||
### <a name="metadata-file"></a> Write build result metadata to the file (--metadata-file)
|
||||
### <a name="metadata-file"></a> Write build result metadata to a file (--metadata-file)
|
||||
|
||||
To output build metadata such as the image digest, pass the `--metadata-file` flag.
|
||||
The metadata will be written as a JSON object to the specified file. The
|
||||
@@ -284,28 +324,11 @@ directory of the specified file must already exist and be writable.
|
||||
$ docker buildx build --load --metadata-file metadata.json .
|
||||
$ cat metadata.json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"containerimage.buildinfo": {
|
||||
"frontend": "dockerfile.v0",
|
||||
"attrs": {
|
||||
"context": "https://github.com/crazy-max/buildkit-buildsources-test.git#master",
|
||||
"filename": "Dockerfile",
|
||||
"source": "docker/dockerfile:master"
|
||||
},
|
||||
"sources": [
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0",
|
||||
"pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0"
|
||||
},
|
||||
{
|
||||
"type": "docker-image",
|
||||
"ref": "docker.io/library/alpine:3.13",
|
||||
"pin": "sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c"
|
||||
}
|
||||
]
|
||||
},
|
||||
"buildx.build.provenance": {},
|
||||
"buildx.build.ref": "mybuilder/mybuilder0/0fjb6ubs52xx3vygf6fgdl611",
|
||||
"containerimage.config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
|
||||
"containerimage.descriptor": {
|
||||
"annotations": {
|
||||
@@ -320,16 +343,80 @@ $ cat metadata.json
|
||||
}
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Build record [provenance](https://docs.docker.com/build/attestations/slsa-provenance/#provenance-attestation-example)
|
||||
> (`buildx.build.provenance`) includes minimal provenance by default. Set the
|
||||
> `BUILDX_METADATA_PROVENANCE` environment variable to customize this behavior:
|
||||
> * `min` sets minimal provenance (default).
|
||||
> * `max` sets full provenance.
|
||||
> * `disabled`, `false` or `0` does not set any provenance.
|
||||
|
||||
### <a name="no-cache-filter"></a> Ignore build cache for specific stages (--no-cache-filter)
|
||||
|
||||
The `--no-cache-filter` lets you specify one or more stages of a multi-stage
|
||||
Dockerfile for which build cache should be ignored. To specify multiple stages,
|
||||
use a comma-separated syntax:
|
||||
|
||||
```console
|
||||
$ docker buildx build --no-cache-filter stage1,stage2,stage3 .
|
||||
```
|
||||
|
||||
For example, the following Dockerfile contains four stages:
|
||||
|
||||
- `base`
|
||||
- `install`
|
||||
- `test`
|
||||
- `release`
|
||||
|
||||
```dockerfile
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM oven/bun:1 as base
|
||||
WORKDIR /app
|
||||
|
||||
FROM base AS install
|
||||
WORKDIR /temp/dev
|
||||
RUN --mount=type=bind,source=package.json,target=package.json \
|
||||
--mount=type=bind,source=bun.lockb,target=bun.lockb \
|
||||
bun install --frozen-lockfile
|
||||
|
||||
FROM base AS test
|
||||
COPY --from=install /temp/dev/node_modules node_modules
|
||||
COPY . .
|
||||
RUN bun test
|
||||
|
||||
FROM base AS release
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=install /temp/dev/node_modules node_modules
|
||||
COPY . .
|
||||
ENTRYPOINT ["bun", "run", "index.js"]
|
||||
```
|
||||
|
||||
To ignore the cache for the `install` stage:
|
||||
|
||||
```console
|
||||
$ docker buildx build --no-cache-filter install .
|
||||
```
|
||||
|
||||
To ignore the cache the `install` and `release` stages:
|
||||
|
||||
```console
|
||||
$ docker buildx build --no-cache-filter install,release .
|
||||
```
|
||||
|
||||
The arguments for the `--no-cache-filter` flag must be names of stages.
|
||||
|
||||
### <a name="output"></a> Set the export action for the build result (-o, --output)
|
||||
|
||||
```
|
||||
```text
|
||||
-o, --output=[PATH,-,type=TYPE[,KEY=VALUE]
|
||||
```
|
||||
|
||||
Sets the export action for the build result. In `docker build` all builds finish
|
||||
by creating a container image and exporting it to `docker images`. `buildx` makes
|
||||
this step configurable allowing results to be exported directly to the client,
|
||||
oci image tarballs, registry etc.
|
||||
OCI image tarballs, registry etc.
|
||||
|
||||
Buildx with `docker` driver currently only supports local, tarball exporter and
|
||||
image exporter. `docker-container` driver supports all the exporters.
|
||||
@@ -341,12 +428,16 @@ exporter and write to `stdout`.
|
||||
```console
|
||||
$ docker buildx build -o . .
|
||||
$ docker buildx build -o outdir .
|
||||
$ docker buildx build -o - - > out.tar
|
||||
$ docker buildx build -o - . > out.tar
|
||||
$ docker buildx build -o type=docker .
|
||||
$ docker buildx build -o type=docker,dest=- . > myimage.tar
|
||||
$ docker buildx build -t tonistiigi/foo -o type=registry
|
||||
```
|
||||
|
||||
> **Note **
|
||||
>
|
||||
> Since BuildKit v0.13.0 multiple outputs can be specified by repeating the flag.
|
||||
|
||||
Supported exported types are:
|
||||
|
||||
#### `local`
|
||||
@@ -384,15 +475,15 @@ The `docker` export type writes the single-platform result image as a [Docker im
|
||||
specification](https://github.com/docker/docker/blob/v20.10.2/image/spec/v1.2.md)
|
||||
tarball on the client. Tarballs created by this exporter are also OCI compatible.
|
||||
|
||||
Currently, multi-platform images cannot be exported with the `docker` export type.
|
||||
The most common usecase for multi-platform images is to directly push to a registry
|
||||
(see [`registry`](#registry)).
|
||||
The default image store in Docker Engine doesn't support loading multi-platform
|
||||
images. You can enable the containerd image store, or push multi-platform images
|
||||
is to directly push to a registry, see [`registry`](#registry).
|
||||
|
||||
Attribute keys:
|
||||
|
||||
- `dest` - destination path where tarball will be written. If not specified the
|
||||
tar will be loaded automatically to the current docker instance.
|
||||
- `context` - name for the docker context where to import the result
|
||||
- `dest` - destination path where tarball will be written. If not specified,
|
||||
the tar will be loaded automatically to the local image store.
|
||||
- `context` - name for the Docker context where to import the result
|
||||
|
||||
#### `image`
|
||||
|
||||
@@ -403,7 +494,7 @@ can be automatically pushed to a registry by specifying attributes.
|
||||
Attribute keys:
|
||||
|
||||
- `name` - name (references) for the new image.
|
||||
- `push` - boolean to automatically push the image.
|
||||
- `push` - Boolean to automatically push the image.
|
||||
|
||||
#### `registry`
|
||||
|
||||
@@ -411,7 +502,7 @@ The `registry` exporter is a shortcut for `type=image,push=true`.
|
||||
|
||||
### <a name="platform"></a> Set the target platforms for the build (--platform)
|
||||
|
||||
```
|
||||
```text
|
||||
--platform=value[,value]
|
||||
```
|
||||
|
||||
@@ -440,12 +531,12 @@ and `arm` architectures. You can see what runtime platforms your current builder
|
||||
instance supports by running `docker buildx inspect --bootstrap`.
|
||||
|
||||
Inside a `Dockerfile`, you can access the current platform value through
|
||||
`TARGETPLATFORM` build argument. Please refer to the [`docker build`
|
||||
documentation](https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope)
|
||||
`TARGETPLATFORM` build argument. Refer to the [`docker build`
|
||||
documentation](https://docs.docker.com/reference/dockerfile/#automatic-platform-args-in-the-global-scope)
|
||||
for the full description of automatic platform argument variants .
|
||||
|
||||
The formatting for the platform specifier is defined in the [containerd source
|
||||
code](https://github.com/containerd/containerd/blob/v1.4.3/platforms/platforms.go#L63).
|
||||
You can find the formatting definition for the platform specifier in the
|
||||
[containerd source code](https://github.com/containerd/containerd/blob/v1.4.3/platforms/platforms.go#L63).
|
||||
|
||||
```console
|
||||
$ docker buildx build --platform=linux/arm64 .
|
||||
@@ -455,11 +546,11 @@ $ docker buildx build --platform=darwin .
|
||||
|
||||
### <a name="progress"></a> Set type of progress output (--progress)
|
||||
|
||||
```
|
||||
```text
|
||||
--progress=VALUE
|
||||
```
|
||||
|
||||
Set type of progress output (auto, plain, tty). Use plain to show container
|
||||
Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container
|
||||
output (default "auto").
|
||||
|
||||
> **Note**
|
||||
@@ -493,15 +584,18 @@ provenance attestations for the build result. For example,
|
||||
`--provenance=mode=max` can be used as an abbreviation for
|
||||
`--attest=type=provenance,mode=max`.
|
||||
|
||||
Additionally, `--provenance` can be used with boolean values to broadly enable
|
||||
or disable provenance attestations. For example, `--provenance=false` can be
|
||||
used to disable all provenance attestations, while `--provenance=true` can be
|
||||
used to enable all provenance attestations.
|
||||
Additionally, `--provenance` can be used with Boolean values to enable or disable
|
||||
provenance attestations. For example, `--provenance=false` disables all provenance attestations,
|
||||
while `--provenance=true` enables all provenance attestations.
|
||||
|
||||
By default, a minimal provenance attestation will be created for the build
|
||||
result, which will only be attached for images pushed to registries.
|
||||
result. Note that the default image store in Docker Engine doesn't support
|
||||
attestations. Provenance attestations only persist for images pushed directly
|
||||
to a registry if you use the default image store. Alternatively, you can switch
|
||||
to using the containerd image store.
|
||||
|
||||
For more information, see [here](https://docs.docker.com/build/attestations/slsa-provenance/).
|
||||
For more information about provenance attestations, see
|
||||
[here](https://docs.docker.com/build/attestations/slsa-provenance/).
|
||||
|
||||
### <a name="push"></a> Push the build result to a registry (--push)
|
||||
|
||||
@@ -515,28 +609,40 @@ attestations for the build result. For example,
|
||||
`--sbom=generator=<user>/<generator-image>` can be used as an abbreviation for
|
||||
`--attest=type=sbom,generator=<user>/<generator-image>`.
|
||||
|
||||
Additionally, `--sbom` can be used with boolean values to broadly enable or
|
||||
disable SBOM attestations. For example, `--sbom=false` can be used to disable
|
||||
all SBOM attestations.
|
||||
Additionally, `--sbom` can be used with Boolean values to enable or disable
|
||||
SBOM attestations. For example, `--sbom=false` disables all SBOM attestations.
|
||||
|
||||
Note that the default image store in Docker Engine doesn't support
|
||||
attestations. Provenance attestations only persist for images pushed directly
|
||||
to a registry if you use the default image store. Alternatively, you can switch
|
||||
to using the containerd image store.
|
||||
|
||||
For more information, see [here](https://docs.docker.com/build/attestations/sbom/).
|
||||
|
||||
### <a name="secret"></a> Secret to expose to the build (--secret)
|
||||
|
||||
```
|
||||
```text
|
||||
--secret=[type=TYPE[,KEY=VALUE]
|
||||
```
|
||||
|
||||
Exposes secret to the build. The secret can be used by the build using
|
||||
[`RUN --mount=type=secret` mount](https://docs.docker.com/engine/reference/builder/#run---mounttypesecret).
|
||||
Exposes secrets (authentication credentials, tokens) to the build.
|
||||
A secret can be mounted into the build using a `RUN --mount=type=secret` mount in the
|
||||
[Dockerfile](https://docs.docker.com/reference/dockerfile/#run---mounttypesecret).
|
||||
For more information about how to use build secrets, see
|
||||
[Build secrets](https://docs.docker.com/build/building/secrets/).
|
||||
|
||||
If `type` is unset it will be detected. Supported types are:
|
||||
Supported types are:
|
||||
|
||||
- [`file`](#file)
|
||||
- [`env`](#env)
|
||||
|
||||
Buildx attempts to detect the `type` automatically if unset.
|
||||
|
||||
#### `file`
|
||||
|
||||
Attribute keys:
|
||||
|
||||
- `id` - ID of the secret. Defaults to basename of the `src` path.
|
||||
- `id` - ID of the secret. Defaults to base name of the `src` path.
|
||||
- `src`, `source` - Secret filename. `id` used if unset.
|
||||
|
||||
```dockerfile
|
||||
@@ -570,15 +676,24 @@ RUN --mount=type=bind,target=. \
|
||||
$ SECRET_TOKEN=token docker buildx build --secret id=SECRET_TOKEN .
|
||||
```
|
||||
|
||||
### <a name="shm-size"></a> Size of /dev/shm (--shm-size)
|
||||
### <a name="shm-size"></a> Shared memory size for build containers (--shm-size)
|
||||
|
||||
Sets the size of the shared memory allocated for build containers when using
|
||||
`RUN` instructions.
|
||||
|
||||
The format is `<number><unit>`. `number` must be greater than `0`. Unit is
|
||||
optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g`
|
||||
(gigabytes). If you omit the unit, the system uses bytes.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> In most cases, it is recommended to let the builder automatically determine
|
||||
> the appropriate configurations. Manual adjustments should only be considered
|
||||
> when specific performance tuning is required for complex build scenarios.
|
||||
|
||||
### <a name="ssh"></a> SSH agent socket or keys to expose to the build (--ssh)
|
||||
|
||||
```
|
||||
```text
|
||||
--ssh=default|<id>[=<socket>|<key>[,<key>]]
|
||||
```
|
||||
|
||||
@@ -586,7 +701,7 @@ This can be useful when some commands in your Dockerfile need specific SSH
|
||||
authentication (e.g., cloning a private repository).
|
||||
|
||||
`--ssh` exposes SSH agent socket or keys to the build and can be used with the
|
||||
[`RUN --mount=type=ssh` mount](https://docs.docker.com/engine/reference/builder/#run---mounttypessh).
|
||||
[`RUN --mount=type=ssh` mount](https://docs.docker.com/reference/dockerfile/#run---mounttypessh).
|
||||
|
||||
Example to access Gitlab using an SSH agent socket:
|
||||
|
||||
@@ -609,7 +724,8 @@ $ docker buildx build --ssh default=$SSH_AUTH_SOCK .
|
||||
|
||||
### <a name="ulimit"></a> Set ulimits (--ulimit)
|
||||
|
||||
`--ulimit` is specified with a soft and hard limit as such:
|
||||
`--ulimit` overrides the default ulimits of build's containers when using `RUN`
|
||||
instructions and are specified with a soft and hard limit as such:
|
||||
`<type>=<soft limit>[:<hard limit>]`, for example:
|
||||
|
||||
```console
|
||||
@@ -618,6 +734,12 @@ $ docker buildx build --ulimit nofile=1024:1024 .
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> If you do not provide a `hard limit`, the `soft limit` is used
|
||||
> for both values. If no `ulimits` are set, they are inherited from
|
||||
> If you don't provide a `hard limit`, the `soft limit` is used
|
||||
> for both values. If no `ulimits` are set, they're inherited from
|
||||
> the default `ulimits` set on the daemon.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> In most cases, it is recommended to let the builder automatically determine
|
||||
> the appropriate configurations. Manual adjustments should only be considered
|
||||
> when specific performance tuning is required for complex build scenarios.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# buildx create
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx create [OPTIONS] [CONTEXT|ENDPOINT]
|
||||
```
|
||||
|
||||
@@ -9,19 +9,19 @@ Create a new builder instance
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------------------------|:--------------|:--------|:----------------------------------------------------------------------|
|
||||
| [`--append`](#append) | | | Append a node to builder instead of changing it |
|
||||
| `--bootstrap` | | | Boot builder after creation |
|
||||
| [`--buildkitd-flags`](#buildkitd-flags) | `string` | | Flags for buildkitd daemon |
|
||||
| [`--config`](#config) | `string` | | BuildKit config file |
|
||||
| [`--driver`](#driver) | `string` | | Driver to use (available: `docker-container`, `kubernetes`, `remote`) |
|
||||
| [`--driver-opt`](#driver-opt) | `stringArray` | | Options for the driver |
|
||||
| [`--leave`](#leave) | | | Remove a node from builder instead of changing it |
|
||||
| [`--name`](#name) | `string` | | Builder instance name |
|
||||
| [`--node`](#node) | `string` | | Create/modify node with given name |
|
||||
| [`--platform`](#platform) | `stringArray` | | Fixed platforms for current node |
|
||||
| [`--use`](#use) | | | Set the current builder instance |
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------------------------------|:--------------|:--------|:----------------------------------------------------------------------|
|
||||
| [`--append`](#append) | | | Append a node to builder instead of changing it |
|
||||
| `--bootstrap` | | | Boot builder after creation |
|
||||
| [`--buildkitd-config`](#buildkitd-config) | `string` | | BuildKit daemon config file |
|
||||
| [`--buildkitd-flags`](#buildkitd-flags) | `string` | | BuildKit daemon flags |
|
||||
| [`--driver`](#driver) | `string` | | Driver to use (available: `docker-container`, `kubernetes`, `remote`) |
|
||||
| [`--driver-opt`](#driver-opt) | `stringArray` | | Options for the driver |
|
||||
| [`--leave`](#leave) | | | Remove a node from builder instead of changing it |
|
||||
| [`--name`](#name) | `string` | | Builder instance name |
|
||||
| [`--node`](#node) | `string` | | Create/modify node with given name |
|
||||
| [`--platform`](#platform) | `stringArray` | | Fixed platforms for current node |
|
||||
| [`--use`](#use) | | | Set the current builder instance |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -29,9 +29,9 @@ Create a new builder instance
|
||||
|
||||
## Description
|
||||
|
||||
Create makes a new builder instance pointing to a docker context or endpoint,
|
||||
Create makes a new builder instance pointing to a Docker context or endpoint,
|
||||
where context is the name of a context from `docker context ls` and endpoint is
|
||||
the address for docker socket (eg. `DOCKER_HOST` value).
|
||||
the address for Docker socket (eg. `DOCKER_HOST` value).
|
||||
|
||||
By default, the current Docker configuration is used for determining the
|
||||
context/endpoint value.
|
||||
@@ -55,31 +55,18 @@ $ docker buildx create --name eager_beaver --append mycontext2
|
||||
eager_beaver
|
||||
```
|
||||
|
||||
### <a name="buildkitd-flags"></a> Specify options for the buildkitd daemon (--buildkitd-flags)
|
||||
### <a name="buildkitd-config"></a> Specify a configuration file for the BuildKit daemon (--buildkitd-config)
|
||||
|
||||
```
|
||||
--buildkitd-flags FLAGS
|
||||
```text
|
||||
--buildkitd-config FILE
|
||||
```
|
||||
|
||||
Adds flags when starting the buildkitd daemon. They take precedence over the
|
||||
configuration file specified by [`--config`](#config). See `buildkitd --help`
|
||||
for the available flags.
|
||||
Specifies the configuration file for the BuildKit daemon to use. The
|
||||
configuration can be overridden by [`--buildkitd-flags`](#buildkitd-flags).
|
||||
See an [example BuildKit daemon configuration file](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md).
|
||||
|
||||
```
|
||||
--buildkitd-flags '--debug --debugaddr 0.0.0.0:6666'
|
||||
```
|
||||
If you don't specify a configuration file, Buildx looks for one by default in:
|
||||
|
||||
### <a name="config"></a> Specify a configuration file for the buildkitd daemon (--config)
|
||||
|
||||
```
|
||||
--config FILE
|
||||
```
|
||||
|
||||
Specifies the configuration file for the buildkitd daemon to use. The configuration
|
||||
can be overridden by [`--buildkitd-flags`](#buildkitd-flags).
|
||||
See an [example buildkitd configuration file](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md).
|
||||
|
||||
If the configuration file is not specified, will look for one by default in:
|
||||
* `$BUILDX_CONFIG/buildkitd.default.toml`
|
||||
* `$DOCKER_CONFIG/buildx/buildkitd.default.toml`
|
||||
* `~/.docker/buildx/buildkitd.default.toml`
|
||||
@@ -89,25 +76,62 @@ certificates for registries in the `buildkitd.toml` configuration, the files
|
||||
will be copied into the container under `/etc/buildkit/certs` and configuration
|
||||
will be updated to reflect that.
|
||||
|
||||
### <a name="buildkitd-flags"></a> Specify options for the BuildKit daemon (--buildkitd-flags)
|
||||
|
||||
```text
|
||||
--buildkitd-flags FLAGS
|
||||
```
|
||||
|
||||
Adds flags when starting the BuildKit daemon. They take precedence over the
|
||||
configuration file specified by [`--buildkitd-config`](#buildkitd-config). See
|
||||
`buildkitd --help` for the available flags.
|
||||
|
||||
```text
|
||||
--buildkitd-flags '--debug --debugaddr 0.0.0.0:6666'
|
||||
```
|
||||
|
||||
#### BuildKit daemon network mode
|
||||
|
||||
You can specify the network mode for the BuildKit daemon with either the
|
||||
configuration file specified by [`--buildkitd-config`](#buildkitd-config) using the
|
||||
`worker.oci.networkMode` option or `--oci-worker-net` flag here. The default
|
||||
value is `auto` and can be one of `bridge`, `cni`, `host`:
|
||||
|
||||
```text
|
||||
--buildkitd-flags '--oci-worker-net bridge'
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Network mode "bridge" is supported since BuildKit v0.13 and will become the
|
||||
> default in next v0.14.
|
||||
|
||||
### <a name="driver"></a> Set the builder driver to use (--driver)
|
||||
|
||||
```
|
||||
```text
|
||||
--driver DRIVER
|
||||
```
|
||||
|
||||
Sets the builder driver to be used. There are two available drivers, each have
|
||||
their own specificities.
|
||||
Sets the builder driver to be used. A driver is a configuration of a BuildKit
|
||||
backend. Buildx supports the following drivers:
|
||||
|
||||
* `docker` (default)
|
||||
* `docker-container`
|
||||
* `kubernetes`
|
||||
* `remote`
|
||||
|
||||
For more information about build drivers, see [here](https://docs.docker.com/build/drivers/).
|
||||
|
||||
#### `docker` driver
|
||||
|
||||
Uses the builder that is built into the docker daemon. With this driver,
|
||||
Uses the builder that is built into the Docker daemon. With this driver,
|
||||
the [`--load`](buildx_build.md#load) flag is implied by default on
|
||||
`buildx build`. However, building multi-platform images or exporting cache is
|
||||
not currently supported.
|
||||
|
||||
#### `docker-container` driver
|
||||
|
||||
Uses a BuildKit container that will be spawned via docker. With this driver,
|
||||
Uses a BuildKit container that will be spawned via Docker. With this driver,
|
||||
both building multi-platform images and exporting cache are supported.
|
||||
|
||||
Unlike `docker` driver, built images will not automatically appear in
|
||||
@@ -116,7 +140,7 @@ to achieve that.
|
||||
|
||||
#### `kubernetes` driver
|
||||
|
||||
Uses a kubernetes pods. With this driver, you can spin up pods with defined
|
||||
Uses Kubernetes pods. With this driver, you can spin up pods with defined
|
||||
BuildKit container image to build your images.
|
||||
|
||||
Unlike `docker` driver, built images will not automatically appear in
|
||||
@@ -125,8 +149,8 @@ to achieve that.
|
||||
|
||||
#### `remote` driver
|
||||
|
||||
Uses a remote instance of buildkitd over an arbitrary connection. With this
|
||||
driver, you manually create and manage instances of buildkit yourself, and
|
||||
Uses a remote instance of BuildKit daemon over an arbitrary connection. With
|
||||
this driver, you manually create and manage instances of buildkit yourself, and
|
||||
configure buildx to point at it.
|
||||
|
||||
Unlike `docker` driver, built images will not automatically appear in
|
||||
@@ -135,48 +159,18 @@ to achieve that.
|
||||
|
||||
### <a name="driver-opt"></a> Set additional driver-specific options (--driver-opt)
|
||||
|
||||
```
|
||||
```text
|
||||
--driver-opt OPTIONS
|
||||
```
|
||||
|
||||
Passes additional driver-specific options.
|
||||
For information about available driver options, refer to the detailed
|
||||
documentation for the specific driver:
|
||||
|
||||
Note: When using quoted values for example for the `nodeselector` or
|
||||
`tolerations` options, ensure that quotes are escaped correctly for your shell.
|
||||
|
||||
#### `docker` driver
|
||||
|
||||
No driver options.
|
||||
|
||||
#### `docker-container` driver
|
||||
|
||||
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||
- `network=NETMODE` - Sets the network mode for running the buildkit container.
|
||||
- `cgroup-parent=CGROUP` - Sets the cgroup parent of the buildkit container if docker is using the "cgroupfs" driver. Defaults to `/docker/buildx`.
|
||||
|
||||
#### `kubernetes` driver
|
||||
|
||||
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||
- `namespace=NS` - Sets the Kubernetes namespace. Defaults to the current namespace.
|
||||
- `replicas=N` - Sets the number of `Pod` replicas. Defaults to 1.
|
||||
- `requests.cpu` - Sets the request CPU value specified in units of Kubernetes CPU. Example `requests.cpu=100m`, `requests.cpu=2`
|
||||
- `requests.memory` - Sets the request memory value specified in bytes or with a valid suffix. Example `requests.memory=500Mi`, `requests.memory=4G`
|
||||
- `limits.cpu` - Sets the limit CPU value specified in units of Kubernetes CPU. Example `limits.cpu=100m`, `limits.cpu=2`
|
||||
- `limits.memory` - Sets the limit memory value specified in bytes or with a valid suffix. Example `limits.memory=500Mi`, `limits.memory=4G`
|
||||
- `serviceaccount` - Sets the created pod's service account. Example `serviceaccount=example-sa`
|
||||
- `"nodeselector=label1=value1,label2=value2"` - Sets the kv of `Pod` nodeSelector. No Defaults. Example `nodeselector=kubernetes.io/arch=arm64`
|
||||
- `"tolerations=key=foo,value=bar;key=foo2,operator=exists;key=foo3,effect=NoSchedule"` - Sets the `Pod` tolerations. Accepts the same values as the kube manifest tolera>tions. Key-value pairs are separated by `,`, tolerations are separated by `;`. No Defaults. Example `tolerations=operator=exists`
|
||||
- `rootless=(true|false)` - Run the container as a non-root user without `securityContext.privileged`. Needs Kubernetes 1.19 or later. [Using Ubuntu host kernel is recommended](https://github.com/moby/buildkit/blob/master/docs/rootless.md). Defaults to false.
|
||||
- `loadbalance=(sticky|random)` - Load-balancing strategy. If set to "sticky", the pod is chosen using the hash of the context path. Defaults to "sticky"
|
||||
- `qemu.install=(true|false)` - Install QEMU emulation for multi platforms support.
|
||||
- `qemu.image=IMAGE` - Sets the QEMU emulation image. Defaults to `tonistiigi/binfmt:latest`
|
||||
|
||||
#### `remote` driver
|
||||
|
||||
- `key=KEY` - Sets the TLS client key.
|
||||
- `cert=CERT` - Sets the TLS client certificate to present to buildkitd.
|
||||
- `cacert=CACERT` - Sets the TLS certificate authority used for validation.
|
||||
- `servername=SERVER` - Sets the TLS server name to be used in requests (defaults to the endpoint hostname).
|
||||
* [`docker` driver](https://docs.docker.com/build/drivers/docker/)
|
||||
* [`docker-container` driver](https://docs.docker.com/build/drivers/docker-container/)
|
||||
* [`kubernetes` driver](https://docs.docker.com/build/drivers/kubernetes/)
|
||||
* [`remote` driver](https://docs.docker.com/build/drivers/remote/)
|
||||
|
||||
### <a name="leave"></a> Remove a node from a builder (--leave)
|
||||
|
||||
@@ -190,7 +184,7 @@ $ docker buildx create --name mybuilder --node mybuilder0 --leave
|
||||
|
||||
### <a name="name"></a> Specify the name of the builder (--name)
|
||||
|
||||
```
|
||||
```text
|
||||
--name NAME
|
||||
```
|
||||
|
||||
@@ -199,17 +193,17 @@ If none is specified, one will be automatically generated.
|
||||
|
||||
### <a name="node"></a> Specify the name of the node (--node)
|
||||
|
||||
```
|
||||
```text
|
||||
--node NODE
|
||||
```
|
||||
|
||||
The `--node` flag specifies the name of the node to be created or modified. If
|
||||
none is specified, it is the name of the builder it belongs to, with an index
|
||||
number suffix.
|
||||
you don't specify a name, the node name defaults to the name of the builder it
|
||||
belongs to, with an index number suffix.
|
||||
|
||||
### <a name="platform"></a> Set the platforms supported by the node (--platform)
|
||||
|
||||
```
|
||||
```text
|
||||
--platform PLATFORMS
|
||||
```
|
||||
|
||||
@@ -221,7 +215,7 @@ building for the same platform.
|
||||
|
||||
```console
|
||||
$ docker buildx create --platform linux/amd64
|
||||
$ docker buildx create --platform linux/arm64,linux/arm/v8
|
||||
$ docker buildx create --platform linux/arm64,linux/arm/v7
|
||||
```
|
||||
|
||||
### <a name="use"></a> Automatically switch to the newly created builder (--use)
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
# docker buildx debug-shell
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Start a monitor
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:---------|:--------|:-----------------------------------------------------------------------------------------|
|
||||
| `--builder` | `string` | | Override the configured builder instance |
|
||||
| `--detach` | | | Detach buildx server (supported only on linux) [experimental] |
|
||||
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||
| `--root` | `string` | | Specify root directory of server to connect [experimental] |
|
||||
| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) [experimental] |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
27
docs/reference/buildx_debug.md
Normal file
27
docs/reference/buildx_debug.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# docker buildx debug
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Start debugger (EXPERIMENTAL)
|
||||
|
||||
### Subcommands
|
||||
|
||||
| Name | Description |
|
||||
|:---------------------------------|:--------------|
|
||||
| [`build`](buildx_debug_build.md) | Start a build |
|
||||
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:------------------|:---------|:--------|:---------------------------------------------------------------------------------------------------------|
|
||||
| `--builder` | `string` | | Override the configured builder instance |
|
||||
| `--detach` | `bool` | `true` | Detach buildx server for the monitor (supported only on linux) (EXPERIMENTAL) |
|
||||
| `--invoke` | `string` | | Launch a monitor with executing specified command (EXPERIMENTAL) |
|
||||
| `--on` | `string` | `error` | When to launch the monitor ([always, error]) (EXPERIMENTAL) |
|
||||
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`) for the monitor. Use plain to show container output |
|
||||
| `--root` | `string` | | Specify root directory of server to connect for the monitor (EXPERIMENTAL) |
|
||||
| `--server-config` | `string` | | Specify buildx server config file for the monitor (used only when launching new server) (EXPERIMENTAL) |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
53
docs/reference/buildx_debug_build.md
Normal file
53
docs/reference/buildx_debug_build.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# docker buildx debug build
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Start a build
|
||||
|
||||
### Aliases
|
||||
|
||||
`docker buildx debug build`, `docker buildx debug b`
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------------------------------------------------------------------------------------------------------------------------|:--------------|:----------|:----------------------------------------------------------------------------------------------------|
|
||||
| [`--add-host`](https://docs.docker.com/reference/cli/docker/image/build/#add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
||||
| `--allow` | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
|
||||
| `--annotation` | `stringArray` | | Add annotation to the image |
|
||||
| `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) |
|
||||
| `--build-arg` | `stringArray` | | Set build-time variables |
|
||||
| `--build-context` | `stringArray` | | Additional build contexts (e.g., name=path) |
|
||||
| `--builder` | `string` | | Override the configured builder instance |
|
||||
| `--cache-from` | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
||||
| `--cache-to` | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
||||
| [`--cgroup-parent`](https://docs.docker.com/reference/cli/docker/image/build/#cgroup-parent) | `string` | | Set the parent cgroup for the `RUN` instructions during build |
|
||||
| `--detach` | | | Detach buildx server (supported only on linux) (EXPERIMENTAL) |
|
||||
| [`-f`](https://docs.docker.com/reference/cli/docker/image/build/#file), [`--file`](https://docs.docker.com/reference/cli/docker/image/build/#file) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
||||
| `--iidfile` | `string` | | Write the image ID to a file |
|
||||
| `--label` | `stringArray` | | Set metadata for an image |
|
||||
| `--load` | | | Shorthand for `--output=type=docker` |
|
||||
| `--metadata-file` | `string` | | Write build result metadata to a file |
|
||||
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
||||
| `--no-cache` | | | Do not use cache when building the image |
|
||||
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
||||
| `-o`, `--output` | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
||||
| `--platform` | `stringArray` | | Set target platform for build |
|
||||
| `--print` | `string` | | Print result of information request (e.g., outline, targets) (EXPERIMENTAL) |
|
||||
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||
| `--provenance` | `string` | | Shorthand for `--attest=type=provenance` |
|
||||
| `--pull` | | | Always attempt to pull all referenced images |
|
||||
| `--push` | | | Shorthand for `--output=type=registry` |
|
||||
| `-q`, `--quiet` | | | Suppress the build output and print image ID on success |
|
||||
| `--root` | `string` | | Specify root directory of server to connect (EXPERIMENTAL) |
|
||||
| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` |
|
||||
| `--secret` | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||
| `--server-config` | `string` | | Specify buildx server config file (used only when launching new server) (EXPERIMENTAL) |
|
||||
| `--shm-size` | `bytes` | `0` | Shared memory size for build containers |
|
||||
| `--ssh` | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||
| [`-t`](https://docs.docker.com/reference/cli/docker/image/build/#tag), [`--tag`](https://docs.docker.com/reference/cli/docker/image/build/#tag) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
||||
| [`--target`](https://docs.docker.com/reference/cli/docker/image/build/#target) | `string` | | Set the target build stage to build |
|
||||
| `--ulimit` | `ulimit` | | Ulimit options |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
47
docs/reference/buildx_dial-stdio.md
Normal file
47
docs/reference/buildx_dial-stdio.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# docker buildx dial-stdio
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Proxy current stdio streams to builder instance
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:-------------|:---------|:--------|:-------------------------------------------------|
|
||||
| `--builder` | `string` | | Override the configured builder instance |
|
||||
| `--platform` | `string` | | Target platform: this is used for node selection |
|
||||
| `--progress` | `string` | `quiet` | Set type of progress output (auto, plain, tty). |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
dial-stdio uses the stdin and stdout streams of the command to proxy to the configured builder instance.
|
||||
It is not intended to be used by humans, but rather by other tools that want to interact with the builder instance via BuildKit API.
|
||||
|
||||
## Examples
|
||||
|
||||
Example go program that uses the dial-stdio command wire up a buildkit client.
|
||||
This is for example use only and may not be suitable for production use.
|
||||
|
||||
```go
|
||||
client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
c1, c2 := net.Pipe()
|
||||
cmd := exec.Command("docker", "buildx", "dial-stdio")
|
||||
cmd.Stdin = c1
|
||||
cmd.Stdout = c1
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
c1.Close()
|
||||
c2.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go func() {
|
||||
cmd.Wait()
|
||||
c2.Close()
|
||||
}()
|
||||
|
||||
return c2
|
||||
}))
|
||||
```
|
||||
@@ -1,6 +1,6 @@
|
||||
# buildx du
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx du
|
||||
```
|
||||
|
||||
@@ -13,13 +13,106 @@ Disk usage
|
||||
|:------------------------|:---------|:--------|:-----------------------------------------|
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| `--filter` | `filter` | | Provide filter values |
|
||||
| `--verbose` | | | Provide a more verbose output |
|
||||
| [`--verbose`](#verbose) | | | Provide a more verbose output |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Examples
|
||||
|
||||
### Show disk usage
|
||||
|
||||
The `docker buildx du` command shows the disk usage for the currently selected
|
||||
builder.
|
||||
|
||||
```console
|
||||
$ docker buildx du
|
||||
ID RECLAIMABLE SIZE LAST ACCESSED
|
||||
12wgll9os87pazzft8lt0yztp* true 1.704GB 13 days ago
|
||||
iupsv3it5ubh92aweb7c1wojc* true 1.297GB 36 minutes ago
|
||||
ek4ve8h4obyv5kld6vicmtqyn true 811.7MB 13 days ago
|
||||
isovrfnmkelzhtdx942w9vjcb* true 811.7MB 13 days ago
|
||||
0jty7mjrndi1yo7xkv1baralh true 810.5MB 13 days ago
|
||||
jyzkefmsysqiaakgwmjgxjpcz* true 810.5MB 13 days ago
|
||||
z8w1y95jn93gvj92jtaj6uhwk true 318MB 2 weeks ago
|
||||
rz2zgfcwlfxsxd7d41w2sz2tt true 8.224kB* 43 hours ago
|
||||
n5bkzpewmk2eiu6hn9tzx18jd true 8.224kB* 43 hours ago
|
||||
ao94g6vtbzdl6k5zgdmrmnwpt true 8.224kB* 43 hours ago
|
||||
2pyjep7njm0wh39vcingxb97i true 8.224kB* 43 hours ago
|
||||
Shared: 115.5MB
|
||||
Private: 10.25GB
|
||||
Reclaimable: 10.36GB
|
||||
Total: 10.36GB
|
||||
```
|
||||
|
||||
If `RECLAIMABLE` is false, the `docker buildx du prune` command won't delete
|
||||
the record, even if you use `--all`. That's because the record is actively in
|
||||
use by some component of the builder.
|
||||
|
||||
The asterisks (\*) in the default output indicate the following:
|
||||
|
||||
- An asterisk next to an ID (`zu7m6evdpebh5h8kfkpw9dlf2*`) indicates that the record
|
||||
is mutable. The size of the record may change, or another build can take ownership of
|
||||
it and change or commit to it. If you run the `du` command again, this item may
|
||||
not be there anymore, or the size might be different.
|
||||
- An asterisk next to a size (`8.288kB*`) indicates that the record is shared.
|
||||
Storage of the record is shared with some other resource, typically an image.
|
||||
If you prune such a record then you will lose build cache but only metadata
|
||||
will be deleted as the image still needs to actual storage layers.
|
||||
|
||||
### <a name="verbose"></a> Use verbose output (--verbose)
|
||||
|
||||
The verbose output of the `docker buildx du` command is useful for inspecting
|
||||
the disk usage records in more detail. The verbose output shows the mutable and
|
||||
shared states more clearly, as well as additional information about the
|
||||
corresponding layer.
|
||||
|
||||
```console
|
||||
$ docker buildx du --verbose
|
||||
...
|
||||
Last used: 2 days ago
|
||||
Type: regular
|
||||
|
||||
ID: 05d0elirb4mmvpmnzbrp3ssrg
|
||||
Parent: e8sfdn4mygrg7msi9ak1dy6op
|
||||
Created at: 2023-11-20 09:53:30.881558721 +0000 UTC
|
||||
Mutable: false
|
||||
Reclaimable: true
|
||||
Shared: false
|
||||
Size: 0B
|
||||
Description: [gobase 3/3] WORKDIR /src
|
||||
Usage count: 3
|
||||
Last used: 24 hours ago
|
||||
Type: regular
|
||||
|
||||
Reclaimable: 4.453GB
|
||||
Total: 4.453GB
|
||||
```
|
||||
|
||||
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||
|
||||
Same as [`buildx --builder`](buildx.md#builder).
|
||||
Use the `--builder` flag to inspect the disk usage of a particular builder.
|
||||
|
||||
```console
|
||||
$ docker buildx du --builder youthful_shtern
|
||||
ID RECLAIMABLE SIZE LAST ACCESSED
|
||||
g41agepgdczekxg2mtw0dujsv* true 1.312GB 47 hours ago
|
||||
e6ycrsa0bn9akigqgzu0sc6kr true 318MB 47 hours ago
|
||||
our9zg4ndly65ze1ccczdksiz true 204.9MB 45 hours ago
|
||||
b7xv3xpxnwupc81tc9ya3mgq6* true 120.6MB 47 hours ago
|
||||
zihgye15ss6vum3wmck9egdoy* true 79.81MB 2 days ago
|
||||
aaydharssv1ug98yhuwclkfrh* true 79.81MB 2 days ago
|
||||
ta1r4vmnjug5dhub76as4kkol* true 74.51MB 47 hours ago
|
||||
murma9f83j9h8miifbq68udjf* true 74.51MB 47 hours ago
|
||||
47f961866a49g5y8myz80ixw1* true 74.51MB 47 hours ago
|
||||
tzh99xtzlaf6txllh3cobag8t true 74.49MB 47 hours ago
|
||||
ld6laoeuo1kwapysu6afwqybl* true 59.89MB 47 hours ago
|
||||
yitxizi5kaplpyomqpos2cryp* true 59.83MB 47 hours ago
|
||||
iy8aa4b7qjn0qmy9wiga9cj8w true 33.65MB 47 hours ago
|
||||
mci7okeijyp8aqqk16j80dy09 true 19.86MB 47 hours ago
|
||||
lqvj091he652slxdla4wom3pz true 14.08MB 47 hours ago
|
||||
fkt31oiv793nd26h42llsjcw7* true 11.87MB 2 days ago
|
||||
uj802yxtvkcjysnjb4kgwvn2v true 11.68MB 45 hours ago
|
||||
Reclaimable: 2.627GB
|
||||
Total: 2.627GB
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# buildx imagetools
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx imagetools [OPTIONS] COMMAND
|
||||
```
|
||||
|
||||
@@ -26,8 +26,9 @@ Commands to work on images in registry
|
||||
|
||||
## Description
|
||||
|
||||
Imagetools contains commands for working with manifest lists in the registry.
|
||||
These commands are useful for inspecting multi-platform build results.
|
||||
The `imagetools` commands contains subcommands for working with manifest lists
|
||||
in container registries. These commands are useful for inspecting manifests
|
||||
to check multi-platform configuration and attestations.
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# buildx imagetools create
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx imagetools create [OPTIONS] [SOURCE] [SOURCE...]
|
||||
```
|
||||
|
||||
@@ -11,6 +11,7 @@ Create a new image based on source images
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:---------------------------------|:--------------|:--------|:-----------------------------------------------------------------------------------------|
|
||||
| [`--annotation`](#annotation) | `stringArray` | | Add annotation to the image |
|
||||
| [`--append`](#append) | | | Append to existing manifest |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| [`--dry-run`](#dry-run) | | | Show final image instead of pushing |
|
||||
@@ -30,6 +31,34 @@ specified, create performs a carbon copy.
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="annotation"></a> Add annotations to an image (--annotation)
|
||||
|
||||
The `--annotation` flag lets you add annotations the image index, manifest,
|
||||
and descriptors when creating a new image.
|
||||
|
||||
The following command creates a `foo/bar:latest` image with the
|
||||
`org.opencontainers.image.authors` annotation on the image index.
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools create \
|
||||
--annotation "index:org.opencontainers.image.authors=dvdksn" \
|
||||
--tag foo/bar:latest \
|
||||
foo/bar:alpha foo/bar:beta foo/bar:gamma
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> The `imagetools create` command supports adding annotations to the image
|
||||
> index and descriptor, using the following type prefixes:
|
||||
>
|
||||
> - `index:`
|
||||
> - `manifest-descriptor:`
|
||||
>
|
||||
> It doesn't support annotating manifests or OCI layouts.
|
||||
|
||||
For more information about annotations, see
|
||||
[Annotations](https://docs.docker.com/build/building/annotations/).
|
||||
|
||||
### <a name="append"></a> Append new sources to an existing manifest list (--append)
|
||||
|
||||
Use the `--append` flag to append the new sources to an existing manifest list
|
||||
@@ -45,7 +74,7 @@ Use the `--dry-run` flag to not push the image, just show it.
|
||||
|
||||
### <a name="file"></a> Read source descriptor from a file (-f, --file)
|
||||
|
||||
```
|
||||
```text
|
||||
-f FILE or --file FILE
|
||||
```
|
||||
|
||||
@@ -66,7 +95,7 @@ The supported fields for the descriptor are defined in [OCI spec](https://github
|
||||
|
||||
### <a name="tag"></a> Set reference for new image (-t, --tag)
|
||||
|
||||
```
|
||||
```text
|
||||
-t IMAGE or --tag IMAGE
|
||||
```
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# buildx imagetools inspect
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx imagetools inspect [OPTIONS] NAME
|
||||
```
|
||||
|
||||
@@ -123,23 +123,93 @@ Manifests:
|
||||
|
||||
#### JSON output
|
||||
|
||||
A `json` go template func is also available if you want to render fields as
|
||||
JSON bytes:
|
||||
A `json` template function is also available if you want to render fields in
|
||||
JSON format:
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect crazymax/loop --format "{{json .Manifest}}"
|
||||
$ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .Manifest}}"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
"digest": "sha256:a9ca35b798e0b198f9be7f3b8b53982e9a6cf96814cb10d78083f40ad8c127f1",
|
||||
"size": 949
|
||||
"schemaVersion": 2,
|
||||
"mediaType": "application/vnd.oci.image.index.v1+json",
|
||||
"digest": "sha256:7007b387ccd52bd42a050f2e8020e56e64622c9269bf7bbe257b326fe99daf19",
|
||||
"size": 855,
|
||||
"manifests": [
|
||||
{
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"digest": "sha256:fbd10fe50b4b174bb9ea273e2eb9827fa8bf5c88edd8635a93dc83e0d1aecb55",
|
||||
"size": 673,
|
||||
"platform": {
|
||||
"architecture": "amd64",
|
||||
"os": "linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||
"digest": "sha256:a9de632c16998489fd63fbca42a03431df00639cfb2ecb8982bf9984b83c5b2b",
|
||||
"size": 839,
|
||||
"annotations": {
|
||||
"vnd.docker.reference.digest": "sha256:fbd10fe50b4b174bb9ea273e2eb9827fa8bf5c88edd8635a93dc83e0d1aecb55",
|
||||
"vnd.docker.reference.type": "attestation-manifest"
|
||||
},
|
||||
"platform": {
|
||||
"architecture": "unknown",
|
||||
"os": "unknown"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .Image}}"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"created": "2022-12-01T11:46:47.713777178Z",
|
||||
"architecture": "amd64",
|
||||
"os": "linux",
|
||||
"config": {
|
||||
"Env": [
|
||||
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
],
|
||||
"Cmd": [
|
||||
"/bin/sh"
|
||||
]
|
||||
},
|
||||
"rootfs": {
|
||||
"type": "layers",
|
||||
"diff_ids": [
|
||||
"sha256:ded7a220bb058e28ee3254fbba04ca90b679070424424761a53a043b93b612bf",
|
||||
"sha256:d85d09ab4b4e921666ccc2db8532e857bf3476b7588e52c9c17741d7af14204f"
|
||||
]
|
||||
},
|
||||
"history": [
|
||||
{
|
||||
"created": "2022-11-22T22:19:28.870801855Z",
|
||||
"created_by": "/bin/sh -c #(nop) ADD file:587cae71969871d3c6456d844a8795df9b64b12c710c275295a1182b46f630e7 in / "
|
||||
},
|
||||
{
|
||||
"created": "2022-11-22T22:19:29.008562326Z",
|
||||
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
|
||||
"empty_layer": true
|
||||
},
|
||||
{
|
||||
"created": "2022-12-01T11:46:47.713777178Z",
|
||||
"created_by": "RUN /bin/sh -c apk add curl # buildkit",
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manifest}}"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"schemaVersion": 2,
|
||||
@@ -284,11 +354,13 @@ $ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manife
|
||||
}
|
||||
```
|
||||
|
||||
Following command provides [SLSA](https://github.com/moby/buildkit/blob/master/docs/attestations/slsa-provenance.md) JSON output:
|
||||
The following command provides [SLSA](https://github.com/moby/buildkit/blob/master/docs/attestations/slsa-provenance.md)
|
||||
JSON output:
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .Provenance}}"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"SLSA": {
|
||||
@@ -343,11 +415,13 @@ $ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .Pr
|
||||
}
|
||||
```
|
||||
|
||||
Following command provides [SBOM](https://github.com/moby/buildkit/blob/master/docs/attestations/sbom.md) JSON output:
|
||||
The following command provides [SBOM](https://github.com/moby/buildkit/blob/master/docs/attestations/sbom.md)
|
||||
JSON output:
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .SBOM}}"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"SPDX": {
|
||||
@@ -372,6 +446,7 @@ $ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .SB
|
||||
```console
|
||||
$ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .}}"
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "crazymax/buildkit:attest",
|
||||
@@ -440,75 +515,6 @@ $ docker buildx imagetools inspect crazymax/buildkit:attest --format "{{json .}}
|
||||
"comment": "buildkit.dockerfile.v0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"Provenance": {
|
||||
"SLSA": {
|
||||
"builder": {
|
||||
"id": ""
|
||||
},
|
||||
"buildType": "https://mobyproject.org/buildkit@v1",
|
||||
"materials": [
|
||||
{
|
||||
"uri": "pkg:docker/docker/buildkit-syft-scanner@stable-1",
|
||||
"digest": {
|
||||
"sha256": "b45f1d207e16c3a3a5a10b254ad8ad358d01f7ea090d382b95c6b2ee2b3ef765"
|
||||
}
|
||||
},
|
||||
{
|
||||
"uri": "pkg:docker/alpine@latest?platform=linux%2Famd64",
|
||||
"digest": {
|
||||
"sha256": "8914eb54f968791faf6a8638949e480fef81e697984fba772b3976835194c6d4"
|
||||
}
|
||||
}
|
||||
],
|
||||
"invocation": {
|
||||
"configSource": {},
|
||||
"parameters": {
|
||||
"frontend": "dockerfile.v0",
|
||||
"locals": [
|
||||
{
|
||||
"name": "context"
|
||||
},
|
||||
{
|
||||
"name": "dockerfile"
|
||||
}
|
||||
]
|
||||
},
|
||||
"environment": {
|
||||
"platform": "linux/amd64"
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"buildInvocationID": "02tdha2xkbxvin87mz9drhag4",
|
||||
"buildStartedOn": "2022-12-01T11:50:07.264704131Z",
|
||||
"buildFinishedOn": "2022-12-01T11:50:08.243788739Z",
|
||||
"reproducible": false,
|
||||
"completeness": {
|
||||
"parameters": true,
|
||||
"environment": true,
|
||||
"materials": false
|
||||
},
|
||||
"https://mobyproject.org/buildkit@v1#metadata": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"SBOM": {
|
||||
"SPDX": {
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"creationInfo": {
|
||||
"created": "2022-12-01T11:46:48.063400162Z",
|
||||
"creators": [
|
||||
"Tool: syft-v0.60.3",
|
||||
"Tool: buildkit-1ace2bb",
|
||||
"Organization: Anchore, Inc"
|
||||
],
|
||||
"licenseListVersion": "3.18"
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentNamespace": "https://anchore.com/syft/dir/run/src/core-0a4ccc6d-1a72-4c3a-a40e-3df1a2ffca94",
|
||||
"files": [...],
|
||||
"spdxVersion": "SPDX-2.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -522,6 +528,7 @@ go template function:
|
||||
```console
|
||||
$ docker buildx imagetools inspect --format '{{json (index .Image "linux/s390x")}}' moby/buildkit:master
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"created": "2022-11-30T17:42:26.414957336Z",
|
||||
@@ -588,15 +595,14 @@ $ docker buildx imagetools inspect --format '{{json (index .Image "linux/s390x")
|
||||
}
|
||||
```
|
||||
|
||||
### <a name="raw"></a> Show original, unformatted JSON manifest (--raw)
|
||||
### <a name="raw"></a> Show original JSON manifest (--raw)
|
||||
|
||||
Use the `--raw` option to print the unformatted JSON manifest bytes.
|
||||
|
||||
> `jq` is used here to get a better rendering of the output result.
|
||||
Use the `--raw` option to print the raw JSON manifest.
|
||||
|
||||
```console
|
||||
$ docker buildx imagetools inspect --raw crazymax/loop | jq
|
||||
$ docker buildx imagetools inspect --raw crazymax/loop
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||
@@ -629,6 +635,7 @@ $ docker buildx imagetools inspect --raw crazymax/loop | jq
|
||||
```console
|
||||
$ docker buildx imagetools inspect --raw moby/buildkit:master | jq
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# buildx inspect
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx inspect [NAME]
|
||||
```
|
||||
|
||||
@@ -27,7 +27,7 @@ Shows information about the current or specified builder.
|
||||
|
||||
Use the `--bootstrap` option to ensure that the builder is running before
|
||||
inspecting it. If the driver is `docker-container`, then `--bootstrap` starts
|
||||
the buildkit container and waits until it is operational. Bootstrapping is
|
||||
the BuildKit container and waits until it's operational. Bootstrapping is
|
||||
automatically done during build, and therefore not necessary. The same BuildKit
|
||||
container is used during the lifetime of the associated builder node (as
|
||||
displayed in `buildx ls`).
|
||||
@@ -45,7 +45,9 @@ The following example shows information about a builder instance named
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Asterisk `*` next to node build platform(s) indicate they had been set manually during `buildx create`. Otherwise, it had been autodetected.
|
||||
> The asterisk (`*`) next to node build platform(s) indicate they have been
|
||||
> manually set during `buildx create`. Otherwise the platforms were
|
||||
> automatically detected.
|
||||
|
||||
```console
|
||||
$ docker buildx inspect elated_tesla
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# buildx install
|
||||
|
||||
```
|
||||
docker buildx install
|
||||
```
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Install buildx as a 'docker builder' alias
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -1,29 +1,84 @@
|
||||
# buildx ls
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx ls
|
||||
```
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
List builder instances
|
||||
|
||||
### Options
|
||||
|
||||
| Name | Type | Default | Description |
|
||||
|:----------------------|:---------|:--------|:------------------|
|
||||
| [`--format`](#format) | `string` | `table` | Format the output |
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
|
||||
## Description
|
||||
|
||||
Lists all builder instances and the nodes for each instance
|
||||
Lists all builder instances and the nodes for each instance.
|
||||
|
||||
```console
|
||||
$ docker buildx ls
|
||||
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
||||
elated_tesla * docker-container
|
||||
elated_tesla0 unix:///var/run/docker.sock running v0.10.3 linux/amd64
|
||||
elated_tesla1 ssh://ubuntu@1.2.3.4 running v0.10.3 linux/arm64*, linux/arm/v7, linux/arm/v6
|
||||
default docker
|
||||
default default running v0.8.2 linux/amd64
|
||||
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
||||
elated_tesla* docker-container
|
||||
\_ elated_tesla0 \_ unix:///var/run/docker.sock running v0.10.3 linux/amd64
|
||||
\_ elated_tesla1 \_ ssh://ubuntu@1.2.3.4 running v0.10.3 linux/arm64*, linux/arm/v7, linux/arm/v6
|
||||
default docker
|
||||
\_ default \_ default running v0.8.2 linux/amd64
|
||||
```
|
||||
|
||||
Each builder has one or more nodes associated with it. The current builder's
|
||||
name is marked with a `*` in `NAME/NODE` and explicit node to build against for
|
||||
the target platform marked with a `*` in the `PLATFORMS` column.
|
||||
|
||||
## Examples
|
||||
|
||||
### <a name="format"></a> Format the output (--format)
|
||||
|
||||
The formatting options (`--format`) pretty-prints builder instances output
|
||||
using a Go template.
|
||||
|
||||
Valid placeholders for the Go template are listed below:
|
||||
|
||||
| Placeholder | Description |
|
||||
|-------------------|---------------------------------------------|
|
||||
| `.Name` | Builder or node name |
|
||||
| `.DriverEndpoint` | Driver (for builder) or Endpoint (for node) |
|
||||
| `.LastActivity` | Builder last activity |
|
||||
| `.Status` | Builder or node status |
|
||||
| `.Buildkit` | BuildKit version of the node |
|
||||
| `.Platforms` | Available node's platforms |
|
||||
| `.Error` | Error |
|
||||
| `.Builder` | Builder object |
|
||||
|
||||
When using the `--format` option, the `ls` command will either output the data
|
||||
exactly as the template declares or, when using the `table` directive, includes
|
||||
column headers as well.
|
||||
|
||||
The following example uses a template without headers and outputs the
|
||||
`Name` and `DriverEndpoint` entries separated by a colon (`:`):
|
||||
|
||||
```console
|
||||
$ docker buildx ls --format "{{.Name}}: {{.DriverEndpoint}}"
|
||||
elated_tesla: docker-container
|
||||
elated_tesla0: unix:///var/run/docker.sock
|
||||
elated_tesla1: ssh://ubuntu@1.2.3.4
|
||||
default: docker
|
||||
default: default
|
||||
```
|
||||
|
||||
The `Builder` placeholder can be used to access the builder object and its
|
||||
fields. For example, the following template outputs the builder's and
|
||||
nodes' names with their respective endpoints:
|
||||
|
||||
```console
|
||||
$ docker buildx ls --format "{{.Builder.Name}}: {{range .Builder.Nodes}}\n {{.Name}}: {{.Endpoint}}{{end}}"
|
||||
elated_tesla:
|
||||
elated_tesla0: unix:///var/run/docker.sock
|
||||
elated_tesla1: ssh://ubuntu@1.2.3.4
|
||||
default: docker
|
||||
default: default
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# buildx prune
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx prune
|
||||
```
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# buildx rm
|
||||
|
||||
```
|
||||
docker buildx rm [NAME]
|
||||
```text
|
||||
docker buildx rm [OPTIONS] [NAME] [NAME...]
|
||||
```
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Remove a builder instance
|
||||
Remove one or more builder instances
|
||||
|
||||
### Options
|
||||
|
||||
@@ -14,7 +14,7 @@ Remove a builder instance
|
||||
| [`--all-inactive`](#all-inactive) | | | Remove all inactive builders |
|
||||
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||
| [`-f`](#force), [`--force`](#force) | | | Do not prompt for confirmation |
|
||||
| [`--keep-daemon`](#keep-daemon) | | | Keep the buildkitd daemon running |
|
||||
| [`--keep-daemon`](#keep-daemon) | | | Keep the BuildKit daemon running |
|
||||
| [`--keep-state`](#keep-state) | | | Keep BuildKit state |
|
||||
|
||||
|
||||
@@ -48,12 +48,15 @@ Do not prompt for confirmation before removing inactive builders.
|
||||
$ docker buildx rm --all-inactive --force
|
||||
```
|
||||
|
||||
### <a name="keep-daemon"></a> Keep the buildkitd daemon running (--keep-daemon)
|
||||
### <a name="keep-daemon"></a> Keep the BuildKit daemon running (--keep-daemon)
|
||||
|
||||
Keep the buildkitd daemon running after the buildx context is removed. This is useful when you manage buildkitd daemons and buildx contexts independently.
|
||||
Currently, only supported by the [`docker-container` and `kubernetes` drivers](buildx_create.md#driver).
|
||||
Keep the BuildKit daemon running after the buildx context is removed. This is
|
||||
useful when you manage BuildKit daemons and buildx contexts independently.
|
||||
Only supported by the
|
||||
[`docker-container`](https://docs.docker.com/build/drivers/docker-container/)
|
||||
and [`kubernetes`](https://docs.docker.com/build/drivers/kubernetes/) drivers.
|
||||
|
||||
### <a name="keep-state"></a> Keep BuildKit state (--keep-state)
|
||||
|
||||
Keep BuildKit state, so it can be reused by a new builder with the same name.
|
||||
Currently, only supported by the [`docker-container` driver](buildx_create.md#driver).
|
||||
Currently, only supported by the [`docker-container` driver](https://docs.docker.com/build/drivers/docker-container/).
|
||||
|
||||
@@ -18,7 +18,7 @@ Stop builder instance
|
||||
|
||||
## Description
|
||||
|
||||
Stops the specified or current builder. This will not prevent buildx build to
|
||||
Stops the specified or current builder. This does not prevent buildx build to
|
||||
restart the builder. The implementation of stop depends on the driver.
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
# buildx uninstall
|
||||
|
||||
```
|
||||
docker buildx uninstall
|
||||
```
|
||||
|
||||
<!---MARKER_GEN_START-->
|
||||
Uninstall the 'docker builder' alias
|
||||
|
||||
|
||||
<!---MARKER_GEN_END-->
|
||||
@@ -1,6 +1,6 @@
|
||||
# buildx version
|
||||
|
||||
```
|
||||
```text
|
||||
docker buildx version
|
||||
```
|
||||
|
||||
@@ -16,5 +16,5 @@ View version information
|
||||
|
||||
```console
|
||||
$ docker buildx version
|
||||
github.com/docker/buildx v0.5.1-docker 11057da37336192bfc57d81e02359ba7ba848e4a
|
||||
github.com/docker/buildx v0.11.2 9872040b6626fb7d87ef7296fd5b832e8cc2ad17
|
||||
```
|
||||
|
||||
@@ -17,30 +17,46 @@ import (
|
||||
"github.com/docker/buildx/util/confutil"
|
||||
"github.com/docker/buildx/util/imagetools"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/docker/cli/opts"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
imagetypes "github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/api/types/system"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/errdefs"
|
||||
dockerarchive "github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/util/tracing/detect"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
volumeStateSuffix = "_state"
|
||||
volumeStateSuffix = "_state"
|
||||
buildkitdConfigFile = "buildkitd.toml"
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
driver.InitConfig
|
||||
factory driver.Factory
|
||||
netMode string
|
||||
image string
|
||||
cgroupParent string
|
||||
env []string
|
||||
factory driver.Factory
|
||||
|
||||
// if you add fields, remember to update docs:
|
||||
// https://github.com/docker/docs/blob/main/content/build/drivers/docker-container.md
|
||||
netMode string
|
||||
image string
|
||||
memory opts.MemBytes
|
||||
memorySwap opts.MemSwapBytes
|
||||
cpuQuota int64
|
||||
cpuPeriod int64
|
||||
cpuShares int64
|
||||
cpusetCpus string
|
||||
cpusetMems string
|
||||
cgroupParent string
|
||||
restartPolicy container.RestartPolicy
|
||||
env []string
|
||||
defaultLoad bool
|
||||
}
|
||||
|
||||
func (d *Driver) IsMobyDriver() bool {
|
||||
@@ -64,10 +80,7 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
|
||||
if err := d.start(ctx, sub); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.wait(ctx, sub); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return d.wait(ctx, sub)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -83,7 +96,7 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rc, err := d.DockerAPI.ImageCreate(ctx, imageName, dockertypes.ImageCreateOptions{
|
||||
rc, err := d.DockerAPI.ImageCreate(ctx, imageName, imagetypes.CreateOptions{
|
||||
RegistryAuth: ra,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -105,14 +118,13 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
||||
Image: imageName,
|
||||
Env: d.env,
|
||||
}
|
||||
if d.InitConfig.BuildkitFlags != nil {
|
||||
cfg.Cmd = d.InitConfig.BuildkitFlags
|
||||
}
|
||||
cfg.Cmd = getBuildkitFlags(d.InitConfig)
|
||||
|
||||
useInit := true // let it cleanup exited processes created by BuildKit's container API
|
||||
if err := l.Wrap("creating container "+d.Name, func() error {
|
||||
return l.Wrap("creating container "+d.Name, func() error {
|
||||
hc := &container.HostConfig{
|
||||
Privileged: true,
|
||||
Privileged: true,
|
||||
RestartPolicy: d.restartPolicy,
|
||||
Mounts: []mount.Mount{
|
||||
{
|
||||
Type: mount.TypeVolume,
|
||||
@@ -125,6 +137,27 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
||||
if d.netMode != "" {
|
||||
hc.NetworkMode = container.NetworkMode(d.netMode)
|
||||
}
|
||||
if d.memory != 0 {
|
||||
hc.Resources.Memory = int64(d.memory)
|
||||
}
|
||||
if d.memorySwap != 0 {
|
||||
hc.Resources.MemorySwap = int64(d.memorySwap)
|
||||
}
|
||||
if d.cpuQuota != 0 {
|
||||
hc.Resources.CPUQuota = d.cpuQuota
|
||||
}
|
||||
if d.cpuPeriod != 0 {
|
||||
hc.Resources.CPUPeriod = d.cpuPeriod
|
||||
}
|
||||
if d.cpuShares != 0 {
|
||||
hc.Resources.CPUShares = d.cpuShares
|
||||
}
|
||||
if d.cpusetCpus != "" {
|
||||
hc.Resources.CpusetCpus = d.cpusetCpus
|
||||
}
|
||||
if d.cpusetMems != "" {
|
||||
hc.Resources.CpusetMems = d.cpusetMems
|
||||
}
|
||||
if info, err := d.DockerAPI.Info(ctx); err == nil {
|
||||
if info.CgroupDriver == "cgroupfs" {
|
||||
// Place all buildkit containers inside this cgroup by default so limits can be attached
|
||||
@@ -135,7 +168,7 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
||||
}
|
||||
}
|
||||
|
||||
secOpts, err := dockertypes.DecodeSecurityOptions(info.SecurityOptions)
|
||||
secOpts, err := system.DecodeSecurityOptions(info.SecurityOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -148,23 +181,19 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
||||
|
||||
}
|
||||
_, err := d.DockerAPI.ContainerCreate(ctx, cfg, hc, &network.NetworkingConfig{}, nil, d.Name)
|
||||
if err != nil {
|
||||
if err != nil && !errdefs.IsConflict(err) {
|
||||
return err
|
||||
}
|
||||
if err := d.copyToContainer(ctx, d.InitConfig.Files); err != nil {
|
||||
return err
|
||||
if err == nil {
|
||||
if err := d.copyToContainer(ctx, d.InitConfig.Files); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.start(ctx, l); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := d.start(ctx, l); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.wait(ctx, l); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return d.wait(ctx, l)
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Driver) wait(ctx context.Context, l progress.SubLogger) error {
|
||||
@@ -198,7 +227,7 @@ func (d *Driver) wait(ctx context.Context, l progress.SubLogger) error {
|
||||
}
|
||||
|
||||
func (d *Driver) copyLogs(ctx context.Context, l progress.SubLogger) error {
|
||||
rc, err := d.DockerAPI.ContainerLogs(ctx, d.Name, dockertypes.ContainerLogsOptions{
|
||||
rc, err := d.DockerAPI.ContainerLogs(ctx, d.Name, container.LogsOptions{
|
||||
ShowStdout: true, ShowStderr: true,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -227,7 +256,9 @@ func (d *Driver) copyToContainer(ctx context.Context, files map[string][]byte) e
|
||||
return err
|
||||
}
|
||||
defer srcArchive.Close()
|
||||
return d.DockerAPI.CopyToContainer(ctx, d.Name, "/", srcArchive, dockertypes.CopyToContainerOptions{})
|
||||
|
||||
baseDir := path.Dir(confutil.DefaultBuildKitConfigDir)
|
||||
return d.DockerAPI.CopyToContainer(ctx, d.Name, baseDir, srcArchive, dockertypes.CopyToContainerOptions{})
|
||||
}
|
||||
|
||||
func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, error) {
|
||||
@@ -274,7 +305,7 @@ func (d *Driver) run(ctx context.Context, cmd []string, stdout, stderr io.Writer
|
||||
}
|
||||
|
||||
func (d *Driver) start(ctx context.Context, l progress.SubLogger) error {
|
||||
return d.DockerAPI.ContainerStart(ctx, d.Name, dockertypes.ContainerStartOptions{})
|
||||
return d.DockerAPI.ContainerStart(ctx, d.Name, container.StartOptions{})
|
||||
}
|
||||
|
||||
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
||||
@@ -332,18 +363,18 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
||||
return err
|
||||
}
|
||||
if info.Status != driver.Inactive {
|
||||
container, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
|
||||
ctr, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rmDaemon {
|
||||
if err := d.DockerAPI.ContainerRemove(ctx, d.Name, dockertypes.ContainerRemoveOptions{
|
||||
if err := d.DockerAPI.ContainerRemove(ctx, d.Name, container.RemoveOptions{
|
||||
RemoveVolumes: true,
|
||||
Force: force,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range container.Mounts {
|
||||
for _, v := range ctr.Mounts {
|
||||
if v.Name != d.Name+volumeStateSuffix {
|
||||
continue
|
||||
}
|
||||
@@ -356,30 +387,30 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||
func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
|
||||
_, conn, err := d.exec(ctx, []string{"buildctl", "dial-stdio"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn = demuxConn(conn)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
exp, err := detect.Exporter()
|
||||
func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error) {
|
||||
conn, err := d.Dial(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var opts []client.ClientOpt
|
||||
var counter int64
|
||||
opts = append(opts, client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
if atomic.AddInt64(&counter, 1) > 1 {
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
return conn, nil
|
||||
}))
|
||||
if td, ok := exp.(client.TracerDelegate); ok {
|
||||
opts = append(opts, client.WithTracerDelegate(td))
|
||||
}
|
||||
opts = append([]client.ClientOpt{
|
||||
client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
if atomic.AddInt64(&counter, 1) > 1 {
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
return conn, nil
|
||||
}),
|
||||
}, opts...)
|
||||
return client.New(ctx, "", opts...)
|
||||
}
|
||||
|
||||
@@ -388,21 +419,19 @@ func (d *Driver) Factory() driver.Factory {
|
||||
}
|
||||
|
||||
func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool {
|
||||
var historyAPI bool
|
||||
c, err := d.Client(ctx)
|
||||
if err == nil {
|
||||
historyAPI = driver.HistoryAPISupported(ctx, c)
|
||||
c.Close()
|
||||
}
|
||||
return map[driver.Feature]bool{
|
||||
driver.OCIExporter: true,
|
||||
driver.DockerExporter: true,
|
||||
driver.CacheExport: true,
|
||||
driver.MultiPlatform: true,
|
||||
driver.HistoryAPI: historyAPI,
|
||||
driver.DefaultLoad: d.defaultLoad,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Driver) HostGatewayIP(ctx context.Context) (net.IP, error) {
|
||||
return nil, errors.New("host-gateway is not supported by the docker-container driver")
|
||||
}
|
||||
|
||||
func demuxConn(c net.Conn) net.Conn {
|
||||
pr, pw := io.Pipe()
|
||||
// TODO: rewrite parser with Reader() to avoid goroutine switch
|
||||
@@ -446,15 +475,34 @@ func writeConfigFiles(m map[string][]byte) (_ string, err error) {
|
||||
os.RemoveAll(tmpDir)
|
||||
}
|
||||
}()
|
||||
configDir := filepath.Base(confutil.DefaultBuildKitConfigDir)
|
||||
for f, dt := range m {
|
||||
f = path.Join(confutil.DefaultBuildKitConfigDir, f)
|
||||
p := filepath.Join(tmpDir, f)
|
||||
if err := os.MkdirAll(filepath.Dir(p), 0700); err != nil {
|
||||
p := filepath.Join(tmpDir, configDir, f)
|
||||
if err := os.MkdirAll(filepath.Dir(p), 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := os.WriteFile(p, dt, 0600); err != nil {
|
||||
if err := os.WriteFile(p, dt, 0644); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return tmpDir, nil
|
||||
}
|
||||
|
||||
func getBuildkitFlags(initConfig driver.InitConfig) []string {
|
||||
flags := initConfig.BuildkitdFlags
|
||||
if _, ok := initConfig.Files[buildkitdConfigFile]; ok {
|
||||
// There's no way for us to determine the appropriate default configuration
|
||||
// path and the default path can vary depending on if the image is normal
|
||||
// or rootless.
|
||||
//
|
||||
// In order to ensure that --config works, copy to a specific path and
|
||||
// specify the location.
|
||||
//
|
||||
// This should be appended before the user-specified arguments
|
||||
// so that this option could be overwritten by the user.
|
||||
newFlags := make([]string, 0, len(flags)+2)
|
||||
newFlags = append(newFlags, "--config", path.Join("/etc/buildkit", buildkitdConfigFile))
|
||||
flags = append(newFlags, flags...)
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
@@ -3,15 +3,18 @@ package docker
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
dockeropts "github.com/docker/cli/opts"
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const prioritySupported = 30
|
||||
const priorityUnsupported = 70
|
||||
const defaultRestartPolicy = "unless-stopped"
|
||||
|
||||
func init() {
|
||||
driver.Register(&factory{})
|
||||
@@ -28,7 +31,7 @@ func (*factory) Usage() string {
|
||||
return "docker-container"
|
||||
}
|
||||
|
||||
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int {
|
||||
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient, dialMeta map[string][]string) int {
|
||||
if api == nil {
|
||||
return priorityUnsupported
|
||||
}
|
||||
@@ -39,18 +42,63 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
||||
if cfg.DockerAPI == nil {
|
||||
return nil, errors.Errorf("%s driver requires docker API access", f.Name())
|
||||
}
|
||||
d := &Driver{factory: f, InitConfig: cfg}
|
||||
rp, err := dockeropts.ParseRestartPolicy(defaultRestartPolicy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d := &Driver{
|
||||
factory: f,
|
||||
InitConfig: cfg,
|
||||
restartPolicy: rp,
|
||||
}
|
||||
for k, v := range cfg.DriverOpts {
|
||||
switch {
|
||||
case k == "network":
|
||||
d.netMode = v
|
||||
if v == "host" {
|
||||
d.InitConfig.BuildkitFlags = append(d.InitConfig.BuildkitFlags, "--allow-insecure-entitlement=network.host")
|
||||
}
|
||||
case k == "image":
|
||||
d.image = v
|
||||
case k == "memory":
|
||||
if err := d.memory.Set(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case k == "memory-swap":
|
||||
if err := d.memorySwap.Set(v); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case k == "cpu-period":
|
||||
vv, err := strconv.ParseInt(v, 10, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.cpuPeriod = vv
|
||||
case k == "cpu-quota":
|
||||
vv, err := strconv.ParseInt(v, 10, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.cpuQuota = vv
|
||||
case k == "cpu-shares":
|
||||
vv, err := strconv.ParseInt(v, 10, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d.cpuShares = vv
|
||||
case k == "cpuset-cpus":
|
||||
d.cpusetCpus = v
|
||||
case k == "cpuset-mems":
|
||||
d.cpusetMems = v
|
||||
case k == "cgroup-parent":
|
||||
d.cgroupParent = v
|
||||
case k == "restart-policy":
|
||||
d.restartPolicy, err = dockeropts.ParseRestartPolicy(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case k == "default-load":
|
||||
d.defaultLoad, err = strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case strings.HasPrefix(k, "env."):
|
||||
envName := strings.TrimPrefix(k, "env.")
|
||||
if envName == "" {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
@@ -14,6 +15,11 @@ import (
|
||||
type Driver struct {
|
||||
factory driver.Factory
|
||||
driver.InitConfig
|
||||
|
||||
// if you add fields, remember to update docs:
|
||||
// https://github.com/docker/docs/blob/main/content/build/drivers/docker.md
|
||||
features features
|
||||
hostGateway hostGateway
|
||||
}
|
||||
|
||||
func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
|
||||
@@ -50,35 +56,83 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
return d.DockerAPI.DialHijack(ctx, "/grpc", "h2c", nil)
|
||||
}), client.WithSessionDialer(func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
||||
return d.DockerAPI.DialHijack(ctx, "/session", proto, meta)
|
||||
}))
|
||||
func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
|
||||
return d.DockerAPI.DialHijack(ctx, "/grpc", "h2c", d.DialMeta)
|
||||
}
|
||||
|
||||
func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error) {
|
||||
opts = append([]client.ClientOpt{
|
||||
client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
return d.Dial(ctx)
|
||||
}), client.WithSessionDialer(func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
||||
return d.DockerAPI.DialHijack(ctx, "/session", proto, meta)
|
||||
}),
|
||||
}, opts...)
|
||||
return client.New(ctx, "", opts...)
|
||||
}
|
||||
|
||||
type features struct {
|
||||
once sync.Once
|
||||
list map[driver.Feature]bool
|
||||
}
|
||||
|
||||
func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool {
|
||||
var useContainerdSnapshotter bool
|
||||
var historyAPI bool
|
||||
c, err := d.Client(ctx)
|
||||
if err == nil {
|
||||
workers, _ := c.ListWorkers(ctx)
|
||||
d.features.once.Do(func() {
|
||||
var useContainerdSnapshotter bool
|
||||
if c, err := d.Client(ctx); err == nil {
|
||||
workers, _ := c.ListWorkers(ctx)
|
||||
for _, w := range workers {
|
||||
if _, ok := w.Labels["org.mobyproject.buildkit.worker.snapshotter"]; ok {
|
||||
useContainerdSnapshotter = true
|
||||
}
|
||||
}
|
||||
c.Close()
|
||||
}
|
||||
d.features.list = map[driver.Feature]bool{
|
||||
driver.OCIExporter: useContainerdSnapshotter,
|
||||
driver.DockerExporter: useContainerdSnapshotter,
|
||||
driver.CacheExport: useContainerdSnapshotter,
|
||||
driver.MultiPlatform: useContainerdSnapshotter,
|
||||
driver.DefaultLoad: true,
|
||||
}
|
||||
})
|
||||
return d.features.list
|
||||
}
|
||||
|
||||
type hostGateway struct {
|
||||
once sync.Once
|
||||
ip net.IP
|
||||
err error
|
||||
}
|
||||
|
||||
func (d *Driver) HostGatewayIP(ctx context.Context) (net.IP, error) {
|
||||
d.hostGateway.once.Do(func() {
|
||||
c, err := d.Client(ctx)
|
||||
if err != nil {
|
||||
d.hostGateway.err = err
|
||||
return
|
||||
}
|
||||
defer c.Close()
|
||||
workers, err := c.ListWorkers(ctx)
|
||||
if err != nil {
|
||||
d.hostGateway.err = errors.Wrap(err, "listing workers")
|
||||
return
|
||||
}
|
||||
for _, w := range workers {
|
||||
if _, ok := w.Labels["org.mobyproject.buildkit.worker.snapshotter"]; ok {
|
||||
useContainerdSnapshotter = true
|
||||
// should match github.com/docker/docker/builder/builder-next/worker/label.HostGatewayIP const
|
||||
if v, ok := w.Labels["org.mobyproject.buildkit.worker.moby.host-gateway-ip"]; ok && v != "" {
|
||||
ip := net.ParseIP(v)
|
||||
if ip == nil {
|
||||
d.hostGateway.err = errors.Errorf("failed to parse host-gateway IP: %s", v)
|
||||
return
|
||||
}
|
||||
d.hostGateway.ip = ip
|
||||
return
|
||||
}
|
||||
}
|
||||
historyAPI = driver.HistoryAPISupported(ctx, c)
|
||||
c.Close()
|
||||
}
|
||||
return map[driver.Feature]bool{
|
||||
driver.OCIExporter: useContainerdSnapshotter,
|
||||
driver.DockerExporter: useContainerdSnapshotter,
|
||||
driver.CacheExport: useContainerdSnapshotter,
|
||||
driver.MultiPlatform: useContainerdSnapshotter,
|
||||
driver.HistoryAPI: historyAPI,
|
||||
}
|
||||
d.hostGateway.err = errors.New("host-gateway IP not found")
|
||||
})
|
||||
return d.hostGateway.ip, d.hostGateway.err
|
||||
}
|
||||
|
||||
func (d *Driver) Factory() driver.Factory {
|
||||
|
||||
@@ -26,12 +26,12 @@ func (*factory) Usage() string {
|
||||
return "docker"
|
||||
}
|
||||
|
||||
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int {
|
||||
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient, dialMeta map[string][]string) int {
|
||||
if api == nil {
|
||||
return priorityUnsupported
|
||||
}
|
||||
|
||||
c, err := api.DialHijack(ctx, "/grpc", "h2c", nil)
|
||||
c, err := api.DialHijack(ctx, "/grpc", "h2c", dialMeta)
|
||||
if err != nil {
|
||||
return priorityUnsupported
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package driver
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/buildx/store"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
@@ -58,13 +60,28 @@ type Driver interface {
|
||||
Version(context.Context) (string, error)
|
||||
Stop(ctx context.Context, force bool) error
|
||||
Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error
|
||||
Client(ctx context.Context) (*client.Client, error)
|
||||
Dial(ctx context.Context) (net.Conn, error)
|
||||
Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error)
|
||||
Features(ctx context.Context) map[Feature]bool
|
||||
HostGatewayIP(ctx context.Context) (net.IP, error)
|
||||
IsMobyDriver() bool
|
||||
Config() InitConfig
|
||||
}
|
||||
|
||||
func Boot(ctx, clientContext context.Context, d Driver, pw progress.Writer) (*client.Client, error) {
|
||||
const builderNamePrefix = "buildx_buildkit_"
|
||||
|
||||
func BuilderName(name string) string {
|
||||
return builderNamePrefix + name
|
||||
}
|
||||
|
||||
func ParseBuilderName(name string) (string, error) {
|
||||
if !strings.HasPrefix(name, builderNamePrefix) {
|
||||
return "", errors.Errorf("invalid builder name %q, must have %q prefix", name, builderNamePrefix)
|
||||
}
|
||||
return strings.TrimPrefix(name, builderNamePrefix), nil
|
||||
}
|
||||
|
||||
func Boot(ctx, clientContext context.Context, d *DriverHandle, pw progress.Writer) (*client.Client, error) {
|
||||
try := 0
|
||||
for {
|
||||
info, err := d.Info(ctx)
|
||||
@@ -92,7 +109,7 @@ func Boot(ctx, clientContext context.Context, d Driver, pw progress.Writer) (*cl
|
||||
}
|
||||
}
|
||||
|
||||
func HistoryAPISupported(ctx context.Context, c *client.Client) bool {
|
||||
func historyAPISupported(ctx context.Context, c *client.Client) bool {
|
||||
cl, err := c.ControlClient().ListenBuildHistory(ctx, &controlapi.BuildHistoryRequest{
|
||||
ActiveOnly: true,
|
||||
Ref: "buildx-test-history-api-feature", // dummy ref to check if the server supports the API
|
||||
|
||||
@@ -6,6 +6,6 @@ const OCIExporter Feature = "OCI exporter"
|
||||
const DockerExporter Feature = "Docker exporter"
|
||||
|
||||
const CacheExport Feature = "Cache export"
|
||||
const MultiPlatform Feature = "Multiple platforms"
|
||||
const MultiPlatform Feature = "Multi-platform build"
|
||||
|
||||
const HistoryAPI Feature = "History API"
|
||||
const DefaultLoad Feature = "Automatically load images to the Docker Engine image store"
|
||||
|
||||
@@ -23,6 +23,7 @@ type EndpointMeta struct {
|
||||
AuthProvider *clientcmdapi.AuthProviderConfig `json:",omitempty"`
|
||||
Exec *clientcmdapi.ExecConfig `json:",omitempty"`
|
||||
UsernamePassword *UsernamePassword `json:"usernamePassword,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
// UsernamePassword contains username/password auth info
|
||||
@@ -77,6 +78,9 @@ func (c *Endpoint) KubernetesConfig() clientcmd.ClientConfig {
|
||||
authInfo.Username = c.UsernamePassword.Username
|
||||
authInfo.Password = c.UsernamePassword.Password
|
||||
}
|
||||
if c.Token != "" {
|
||||
authInfo.Token = c.Token
|
||||
}
|
||||
authInfo.AuthProvider = c.AuthProvider
|
||||
authInfo.Exec = c.Exec
|
||||
cfg.Clusters["cluster"] = cluster
|
||||
|
||||
@@ -68,6 +68,7 @@ func FromKubeConfig(kubeconfig, kubeContext, namespaceOverride string) (Endpoint
|
||||
AuthProvider: clientcfg.AuthProvider,
|
||||
Exec: clientcfg.ExecProvider,
|
||||
UsernamePassword: usernamePassword,
|
||||
Token: clientcfg.BearerToken,
|
||||
},
|
||||
TLSData: tlsData,
|
||||
}, nil
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/util/tracing/detect"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
@@ -38,7 +37,10 @@ const (
|
||||
|
||||
type Driver struct {
|
||||
driver.InitConfig
|
||||
factory driver.Factory
|
||||
factory driver.Factory
|
||||
|
||||
// if you add fields, remember to update docs:
|
||||
// https://github.com/docker/docs/blob/main/content/build/drivers/kubernetes.md
|
||||
minReplicas int
|
||||
deployment *appsv1.Deployment
|
||||
configMaps []*corev1.ConfigMap
|
||||
@@ -47,6 +49,7 @@ type Driver struct {
|
||||
podClient clientcorev1.PodInterface
|
||||
configMapClient clientcorev1.ConfigMapInterface
|
||||
podChooser podchooser.PodChooser
|
||||
defaultLoad bool
|
||||
}
|
||||
|
||||
func (d *Driver) IsMobyDriver() bool {
|
||||
@@ -87,10 +90,7 @@ func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
|
||||
return sub.Wrap(
|
||||
fmt.Sprintf("waiting for %d pods to be ready", d.minReplicas),
|
||||
func() error {
|
||||
if err := d.wait(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return d.wait(ctx)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -189,7 +189,7 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||
func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
|
||||
restClient := d.clientset.CoreV1().RESTClient()
|
||||
restClientConfig, err := d.KubeClientConfig.ClientConfig()
|
||||
if err != nil {
|
||||
@@ -208,19 +208,15 @@ func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
exp, err := detect.Exporter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var opts []client.ClientOpt
|
||||
opts = append(opts, client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
return conn, nil
|
||||
}))
|
||||
if td, ok := exp.(client.TracerDelegate); ok {
|
||||
opts = append(opts, client.WithTracerDelegate(td))
|
||||
}
|
||||
func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error) {
|
||||
opts = append([]client.ClientOpt{
|
||||
client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||
return d.Dial(ctx)
|
||||
}),
|
||||
}, opts...)
|
||||
return client.New(ctx, "", opts...)
|
||||
}
|
||||
|
||||
@@ -228,18 +224,16 @@ func (d *Driver) Factory() driver.Factory {
|
||||
return d.factory
|
||||
}
|
||||
|
||||
func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool {
|
||||
var historyAPI bool
|
||||
c, err := d.Client(ctx)
|
||||
if err == nil {
|
||||
historyAPI = driver.HistoryAPISupported(ctx, c)
|
||||
c.Close()
|
||||
}
|
||||
func (d *Driver) Features(_ context.Context) map[driver.Feature]bool {
|
||||
return map[driver.Feature]bool{
|
||||
driver.OCIExporter: true,
|
||||
driver.DockerExporter: d.DockerAPI != nil,
|
||||
driver.CacheExport: true,
|
||||
driver.MultiPlatform: true, // Untested (needs multiple Driver instances)
|
||||
driver.HistoryAPI: historyAPI,
|
||||
driver.DefaultLoad: d.defaultLoad,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Driver) HostGatewayIP(_ context.Context) (net.IP, error) {
|
||||
return nil, errors.New("host-gateway is not supported by the kubernetes driver")
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func (*factory) Usage() string {
|
||||
return DriverName
|
||||
}
|
||||
|
||||
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int {
|
||||
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient, dialMeta map[string][]string) int {
|
||||
if api == nil {
|
||||
return priorityUnsupported
|
||||
}
|
||||
@@ -68,11 +68,13 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
||||
clientset: clientset,
|
||||
}
|
||||
|
||||
deploymentOpt, loadbalance, namespace, err := f.processDriverOpts(deploymentName, namespace, cfg)
|
||||
deploymentOpt, loadbalance, namespace, defaultLoad, err := f.processDriverOpts(deploymentName, namespace, cfg)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.defaultLoad = defaultLoad
|
||||
|
||||
d.deployment, d.configMaps, err = manifest.NewDeployment(deploymentOpt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -100,17 +102,19 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg driver.InitConfig) (*manifest.DeploymentOpt, string, string, error) {
|
||||
func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg driver.InitConfig) (*manifest.DeploymentOpt, string, string, bool, error) {
|
||||
deploymentOpt := &manifest.DeploymentOpt{
|
||||
Name: deploymentName,
|
||||
Image: bkimage.DefaultImage,
|
||||
Replicas: 1,
|
||||
BuildkitFlags: cfg.BuildkitFlags,
|
||||
BuildkitFlags: cfg.BuildkitdFlags,
|
||||
Rootless: false,
|
||||
Platforms: cfg.Platforms,
|
||||
ConfigFiles: cfg.Files,
|
||||
}
|
||||
|
||||
defaultLoad := false
|
||||
|
||||
deploymentOpt.Qemu.Image = bkimage.QemuImage
|
||||
|
||||
loadbalance := LoadbalanceSticky
|
||||
@@ -127,20 +131,24 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
|
||||
case "replicas":
|
||||
deploymentOpt.Replicas, err = strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", false, err
|
||||
}
|
||||
case "requests.cpu":
|
||||
deploymentOpt.RequestsCPU = v
|
||||
case "requests.memory":
|
||||
deploymentOpt.RequestsMemory = v
|
||||
case "requests.ephemeral-storage":
|
||||
deploymentOpt.RequestsEphemeralStorage = v
|
||||
case "limits.cpu":
|
||||
deploymentOpt.LimitsCPU = v
|
||||
case "limits.memory":
|
||||
deploymentOpt.LimitsMemory = v
|
||||
case "limits.ephemeral-storage":
|
||||
deploymentOpt.LimitsEphemeralStorage = v
|
||||
case "rootless":
|
||||
deploymentOpt.Rootless, err = strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", false, err
|
||||
}
|
||||
if _, isImage := cfg.DriverOpts["image"]; !isImage {
|
||||
deploymentOpt.Image = bkimage.DefaultRootlessImage
|
||||
@@ -148,15 +156,20 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
|
||||
case "serviceaccount":
|
||||
deploymentOpt.ServiceAccountName = v
|
||||
case "nodeselector":
|
||||
kvs := strings.Split(strings.Trim(v, `"`), ",")
|
||||
s := map[string]string{}
|
||||
for i := range kvs {
|
||||
kv := strings.Split(kvs[i], "=")
|
||||
if len(kv) == 2 {
|
||||
s[kv[0]] = kv[1]
|
||||
}
|
||||
deploymentOpt.NodeSelector, err = splitMultiValues(v, ",", "=")
|
||||
if err != nil {
|
||||
return nil, "", "", false, errors.Wrap(err, "cannot parse node selector")
|
||||
}
|
||||
case "annotations":
|
||||
deploymentOpt.CustomAnnotations, err = splitMultiValues(v, ",", "=")
|
||||
if err != nil {
|
||||
return nil, "", "", false, errors.Wrap(err, "cannot parse annotations")
|
||||
}
|
||||
case "labels":
|
||||
deploymentOpt.CustomLabels, err = splitMultiValues(v, ",", "=")
|
||||
if err != nil {
|
||||
return nil, "", "", false, errors.Wrap(err, "cannot parse labels")
|
||||
}
|
||||
deploymentOpt.NodeSelector = s
|
||||
case "tolerations":
|
||||
ts := strings.Split(v, ";")
|
||||
deploymentOpt.Tolerations = []corev1.Toleration{}
|
||||
@@ -180,12 +193,12 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
|
||||
case "tolerationSeconds":
|
||||
c, err := strconv.Atoi(kv[1])
|
||||
if nil != err {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", false, err
|
||||
}
|
||||
c64 := int64(c)
|
||||
t.TolerationSeconds = &c64
|
||||
default:
|
||||
return nil, "", "", errors.Errorf("invalid tolaration %q", v)
|
||||
return nil, "", "", false, errors.Errorf("invalid tolaration %q", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,24 +210,42 @@ func (f *factory) processDriverOpts(deploymentName string, namespace string, cfg
|
||||
case LoadbalanceSticky:
|
||||
case LoadbalanceRandom:
|
||||
default:
|
||||
return nil, "", "", errors.Errorf("invalid loadbalance %q", v)
|
||||
return nil, "", "", false, errors.Errorf("invalid loadbalance %q", v)
|
||||
}
|
||||
loadbalance = v
|
||||
case "qemu.install":
|
||||
deploymentOpt.Qemu.Install, err = strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
return nil, "", "", false, err
|
||||
}
|
||||
case "qemu.image":
|
||||
if v != "" {
|
||||
deploymentOpt.Qemu.Image = v
|
||||
}
|
||||
case "default-load":
|
||||
defaultLoad, err = strconv.ParseBool(v)
|
||||
if err != nil {
|
||||
return nil, "", "", false, err
|
||||
}
|
||||
default:
|
||||
return nil, "", "", errors.Errorf("invalid driver option %s for driver %s", k, DriverName)
|
||||
return nil, "", "", false, errors.Errorf("invalid driver option %s for driver %s", k, DriverName)
|
||||
}
|
||||
}
|
||||
|
||||
return deploymentOpt, loadbalance, namespace, nil
|
||||
return deploymentOpt, loadbalance, namespace, defaultLoad, nil
|
||||
}
|
||||
|
||||
func splitMultiValues(in string, itemsep string, kvsep string) (map[string]string, error) {
|
||||
kvs := strings.Split(strings.Trim(in, `"`), itemsep)
|
||||
s := map[string]string{}
|
||||
for i := range kvs {
|
||||
kv := strings.Split(kvs[i], kvsep)
|
||||
if len(kv) != 2 {
|
||||
return nil, errors.Errorf("invalid key-value pair: %s", kvs[i])
|
||||
}
|
||||
s[kv[0]] = kv[1]
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (f *factory) AllowsInstances() bool {
|
||||
@@ -226,10 +257,10 @@ func (f *factory) AllowsInstances() bool {
|
||||
// eg. "buildx_buildkit_loving_mendeleev0" -> "loving-mendeleev0"
|
||||
func buildxNameToDeploymentName(bx string) (string, error) {
|
||||
// TODO: commands.util.go should not pass "buildx_buildkit_" prefix to drivers
|
||||
if !strings.HasPrefix(bx, "buildx_buildkit_") {
|
||||
return "", errors.Errorf("expected a string with \"buildx_buildkit_\", got %q", bx)
|
||||
s, err := driver.ParseBuilderName(bx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
s := strings.TrimPrefix(bx, "buildx_buildkit_")
|
||||
s = strings.ReplaceAll(s, "_", "-")
|
||||
return s, nil
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
}
|
||||
|
||||
cfg := driver.InitConfig{
|
||||
Name: "buildx_buildkit_test",
|
||||
Name: driver.BuilderName("test"),
|
||||
KubeClientConfig: &kcc,
|
||||
}
|
||||
f := factory{}
|
||||
@@ -47,13 +47,14 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
"rootless": "true",
|
||||
"nodeselector": "selector1=value1,selector2=value2",
|
||||
"tolerations": "key=tolerationKey1,value=tolerationValue1,operator=Equal,effect=NoSchedule,tolerationSeconds=60;key=tolerationKey2,operator=Exists",
|
||||
"annotations": "example.com/expires-after=annotation1,example.com/other=annotation2",
|
||||
"labels": "example.com/owner=label1,example.com/other=label2",
|
||||
"loadbalance": "random",
|
||||
"qemu.install": "true",
|
||||
"qemu.image": "qemu:latest",
|
||||
"default-load": "true",
|
||||
}
|
||||
ns := "test"
|
||||
|
||||
r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, ns, cfg)
|
||||
r, loadbalance, ns, defaultLoad, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
|
||||
nodeSelectors := map[string]string{
|
||||
"selector1": "value1",
|
||||
@@ -75,6 +76,16 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
customAnnotations := map[string]string{
|
||||
"example.com/expires-after": "annotation1",
|
||||
"example.com/other": "annotation2",
|
||||
}
|
||||
|
||||
customLabels := map[string]string{
|
||||
"example.com/owner": "label1",
|
||||
"example.com/other": "label2",
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, "test-ns", ns)
|
||||
@@ -86,10 +97,13 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
require.Equal(t, "64Mi", r.LimitsMemory)
|
||||
require.True(t, r.Rootless)
|
||||
require.Equal(t, nodeSelectors, r.NodeSelector)
|
||||
require.Equal(t, customAnnotations, r.CustomAnnotations)
|
||||
require.Equal(t, customLabels, r.CustomLabels)
|
||||
require.Equal(t, tolerations, r.Tolerations)
|
||||
require.Equal(t, LoadbalanceRandom, loadbalance)
|
||||
require.True(t, r.Qemu.Install)
|
||||
require.Equal(t, "qemu:latest", r.Qemu.Image)
|
||||
require.True(t, defaultLoad)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -97,7 +111,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
"NoOptions", func(t *testing.T) {
|
||||
cfg.DriverOpts = map[string]string{}
|
||||
|
||||
r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
r, loadbalance, ns, defaultLoad, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -110,10 +124,13 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
require.Equal(t, "", r.LimitsMemory)
|
||||
require.False(t, r.Rootless)
|
||||
require.Empty(t, r.NodeSelector)
|
||||
require.Empty(t, r.CustomAnnotations)
|
||||
require.Empty(t, r.CustomLabels)
|
||||
require.Empty(t, r.Tolerations)
|
||||
require.Equal(t, LoadbalanceSticky, loadbalance)
|
||||
require.False(t, r.Qemu.Install)
|
||||
require.Equal(t, bkimage.QemuImage, r.Qemu.Image)
|
||||
require.False(t, defaultLoad)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -124,7 +141,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
"loadbalance": "sticky",
|
||||
}
|
||||
|
||||
r, loadbalance, ns, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
r, loadbalance, ns, defaultLoad, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -137,10 +154,13 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
require.Equal(t, "", r.LimitsMemory)
|
||||
require.True(t, r.Rootless)
|
||||
require.Empty(t, r.NodeSelector)
|
||||
require.Empty(t, r.CustomAnnotations)
|
||||
require.Empty(t, r.CustomLabels)
|
||||
require.Empty(t, r.Tolerations)
|
||||
require.Equal(t, LoadbalanceSticky, loadbalance)
|
||||
require.False(t, r.Qemu.Install)
|
||||
require.Equal(t, bkimage.QemuImage, r.Qemu.Image)
|
||||
require.False(t, defaultLoad)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -149,9 +169,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
cfg.DriverOpts = map[string]string{
|
||||
"replicas": "invalid",
|
||||
}
|
||||
|
||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
|
||||
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
@@ -161,9 +179,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
cfg.DriverOpts = map[string]string{
|
||||
"rootless": "invalid",
|
||||
}
|
||||
|
||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
|
||||
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
@@ -173,9 +189,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
cfg.DriverOpts = map[string]string{
|
||||
"tolerations": "key=foo,value=bar,invalid=foo2",
|
||||
}
|
||||
|
||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
|
||||
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
@@ -185,9 +199,27 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
cfg.DriverOpts = map[string]string{
|
||||
"tolerations": "key=foo,value=bar,tolerationSeconds=invalid",
|
||||
}
|
||||
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
|
||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
t.Run(
|
||||
"InvalidCustomAnnotation", func(t *testing.T) {
|
||||
cfg.DriverOpts = map[string]string{
|
||||
"annotations": "key,value",
|
||||
}
|
||||
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
|
||||
t.Run(
|
||||
"InvalidCustomLabel", func(t *testing.T) {
|
||||
cfg.DriverOpts = map[string]string{
|
||||
"labels": "key=value=foo",
|
||||
}
|
||||
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
@@ -197,9 +229,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
cfg.DriverOpts = map[string]string{
|
||||
"loadbalance": "invalid",
|
||||
}
|
||||
|
||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
|
||||
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
@@ -209,9 +239,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
cfg.DriverOpts = map[string]string{
|
||||
"qemu.install": "invalid",
|
||||
}
|
||||
|
||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
|
||||
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
@@ -221,9 +249,7 @@ func TestFactory_processDriverOpts(t *testing.T) {
|
||||
cfg.DriverOpts = map[string]string{
|
||||
"invalid": "foo",
|
||||
}
|
||||
|
||||
_, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
|
||||
_, _, _, _, err := f.processDriverOpts(cfg.Name, "test", cfg)
|
||||
require.Error(t, err)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/docker/buildx/util/platformutil"
|
||||
v1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
@@ -31,24 +32,34 @@ type DeploymentOpt struct {
|
||||
// files mounted at /etc/buildkitd
|
||||
ConfigFiles map[string][]byte
|
||||
|
||||
Rootless bool
|
||||
NodeSelector map[string]string
|
||||
Tolerations []corev1.Toleration
|
||||
RequestsCPU string
|
||||
RequestsMemory string
|
||||
LimitsCPU string
|
||||
LimitsMemory string
|
||||
Platforms []v1.Platform
|
||||
Rootless bool
|
||||
NodeSelector map[string]string
|
||||
CustomAnnotations map[string]string
|
||||
CustomLabels map[string]string
|
||||
Tolerations []corev1.Toleration
|
||||
RequestsCPU string
|
||||
RequestsMemory string
|
||||
RequestsEphemeralStorage string
|
||||
LimitsCPU string
|
||||
LimitsMemory string
|
||||
LimitsEphemeralStorage string
|
||||
Platforms []v1.Platform
|
||||
}
|
||||
|
||||
const (
|
||||
containerName = "buildkitd"
|
||||
AnnotationPlatform = "buildx.docker.com/platform"
|
||||
LabelApp = "app"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrReservedAnnotationPlatform = errors.Errorf("the annotation \"%s\" is reserved and cannot be customized", AnnotationPlatform)
|
||||
ErrReservedLabelApp = errors.Errorf("the label \"%s\" is reserved and cannot be customized", LabelApp)
|
||||
)
|
||||
|
||||
func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.ConfigMap, err error) {
|
||||
labels := map[string]string{
|
||||
"app": opt.Name,
|
||||
LabelApp: opt.Name,
|
||||
}
|
||||
annotations := map[string]string{}
|
||||
replicas := int32(opt.Replicas)
|
||||
@@ -59,6 +70,20 @@ func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.Config
|
||||
annotations[AnnotationPlatform] = strings.Join(platformutil.Format(opt.Platforms), ",")
|
||||
}
|
||||
|
||||
for k, v := range opt.CustomAnnotations {
|
||||
if k == AnnotationPlatform {
|
||||
return nil, nil, ErrReservedAnnotationPlatform
|
||||
}
|
||||
annotations[k] = v
|
||||
}
|
||||
|
||||
for k, v := range opt.CustomLabels {
|
||||
if k == LabelApp {
|
||||
return nil, nil, ErrReservedLabelApp
|
||||
}
|
||||
labels[k] = v
|
||||
}
|
||||
|
||||
d = &appsv1.Deployment{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: appsv1.SchemeGroupVersion.String(),
|
||||
@@ -182,6 +207,14 @@ func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.Config
|
||||
d.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceMemory] = reqMemory
|
||||
}
|
||||
|
||||
if opt.RequestsEphemeralStorage != "" {
|
||||
reqEphemeralStorage, err := resource.ParseQuantity(opt.RequestsEphemeralStorage)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
d.Spec.Template.Spec.Containers[0].Resources.Requests[corev1.ResourceEphemeralStorage] = reqEphemeralStorage
|
||||
}
|
||||
|
||||
if opt.LimitsCPU != "" {
|
||||
limCPU, err := resource.ParseQuantity(opt.LimitsCPU)
|
||||
if err != nil {
|
||||
@@ -198,6 +231,14 @@ func NewDeployment(opt *DeploymentOpt) (d *appsv1.Deployment, c []*corev1.Config
|
||||
d.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceMemory] = limMemory
|
||||
}
|
||||
|
||||
if opt.LimitsEphemeralStorage != "" {
|
||||
limEphemeralStorage, err := resource.ParseQuantity(opt.LimitsEphemeralStorage)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
d.Spec.Template.Spec.Containers[0].Resources.Limits[corev1.ResourceEphemeralStorage] = limEphemeralStorage
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -7,18 +7,18 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"k8s.io/client-go/rest"
|
||||
|
||||
dockerclient "github.com/docker/docker/client"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/util/tracing/detect"
|
||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type Factory interface {
|
||||
Name() string
|
||||
Usage() string
|
||||
Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int
|
||||
Priority(ctx context.Context, endpoint string, api dockerclient.APIClient, dialMeta map[string][]string) int
|
||||
New(ctx context.Context, cfg InitConfig) (Driver, error)
|
||||
AllowsInstances() bool
|
||||
}
|
||||
@@ -53,13 +53,13 @@ type InitConfig struct {
|
||||
EndpointAddr string
|
||||
DockerAPI dockerclient.APIClient
|
||||
KubeClientConfig KubeClientConfig
|
||||
BuildkitFlags []string
|
||||
BuildkitdFlags []string
|
||||
Files map[string][]byte
|
||||
DriverOpts map[string]string
|
||||
Auth Auth
|
||||
Platforms []specs.Platform
|
||||
// ContextPathHash can be used for determining pods in the driver instance
|
||||
ContextPathHash string
|
||||
ContextPathHash string // can be used for determining pods in the driver instance
|
||||
DialMeta map[string][]string
|
||||
}
|
||||
|
||||
var drivers map[string]Factory
|
||||
@@ -71,7 +71,7 @@ func Register(f Factory) {
|
||||
drivers[f.Name()] = f
|
||||
}
|
||||
|
||||
func GetDefaultFactory(ctx context.Context, ep string, c dockerclient.APIClient, instanceRequired bool) (Factory, error) {
|
||||
func GetDefaultFactory(ctx context.Context, ep string, c dockerclient.APIClient, instanceRequired bool, dialMeta map[string][]string) (Factory, error) {
|
||||
if len(drivers) == 0 {
|
||||
return nil, errors.Errorf("no drivers available")
|
||||
}
|
||||
@@ -84,7 +84,7 @@ func GetDefaultFactory(ctx context.Context, ep string, c dockerclient.APIClient,
|
||||
if instanceRequired && !f.AllowsInstances() {
|
||||
continue
|
||||
}
|
||||
dd = append(dd, p{f: f, priority: f.Priority(ctx, ep, c)})
|
||||
dd = append(dd, p{f: f, priority: f.Priority(ctx, ep, c, dialMeta)})
|
||||
}
|
||||
sort.Slice(dd, func(i, j int) bool {
|
||||
return dd[i].priority < dd[j].priority
|
||||
@@ -104,22 +104,23 @@ func GetFactory(name string, instanceRequired bool) (Factory, error) {
|
||||
return nil, errors.Errorf("failed to find driver %q", name)
|
||||
}
|
||||
|
||||
func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, flags []string, files map[string][]byte, do map[string]string, platforms []specs.Platform, contextPathHash string) (Driver, error) {
|
||||
func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string, api dockerclient.APIClient, auth Auth, kcc KubeClientConfig, buildkitdFlags []string, files map[string][]byte, do map[string]string, platforms []specs.Platform, contextPathHash string, dialMeta map[string][]string) (*DriverHandle, error) {
|
||||
ic := InitConfig{
|
||||
EndpointAddr: endpointAddr,
|
||||
DockerAPI: api,
|
||||
KubeClientConfig: kcc,
|
||||
Name: name,
|
||||
BuildkitFlags: flags,
|
||||
BuildkitdFlags: buildkitdFlags,
|
||||
DriverOpts: do,
|
||||
Auth: auth,
|
||||
Platforms: platforms,
|
||||
ContextPathHash: contextPathHash,
|
||||
DialMeta: dialMeta,
|
||||
Files: files,
|
||||
}
|
||||
if f == nil {
|
||||
var err error
|
||||
f, err = GetDefaultFactory(ctx, endpointAddr, api, false)
|
||||
f, err = GetDefaultFactory(ctx, endpointAddr, api, false, dialMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -128,7 +129,7 @@ func GetDriver(ctx context.Context, name string, f Factory, endpointAddr string,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cachedDriver{Driver: d}, nil
|
||||
return &DriverHandle{Driver: d}, nil
|
||||
}
|
||||
|
||||
func GetFactories(instanceRequired bool) []Factory {
|
||||
@@ -145,25 +146,44 @@ func GetFactories(instanceRequired bool) []Factory {
|
||||
return ds
|
||||
}
|
||||
|
||||
type cachedDriver struct {
|
||||
type DriverHandle struct {
|
||||
Driver
|
||||
client *client.Client
|
||||
err error
|
||||
once sync.Once
|
||||
featuresOnce sync.Once
|
||||
features map[Feature]bool
|
||||
client *client.Client
|
||||
err error
|
||||
once sync.Once
|
||||
historyAPISupportedOnce sync.Once
|
||||
historyAPISupported bool
|
||||
}
|
||||
|
||||
func (d *cachedDriver) Client(ctx context.Context) (*client.Client, error) {
|
||||
func (d *DriverHandle) Client(ctx context.Context) (*client.Client, error) {
|
||||
d.once.Do(func() {
|
||||
d.client, d.err = d.Driver.Client(ctx)
|
||||
opts, err := d.getClientOptions()
|
||||
if err != nil {
|
||||
d.err = err
|
||||
return
|
||||
}
|
||||
d.client, d.err = d.Driver.Client(ctx, opts...)
|
||||
})
|
||||
return d.client, d.err
|
||||
}
|
||||
|
||||
func (d *cachedDriver) Features(ctx context.Context) map[Feature]bool {
|
||||
d.featuresOnce.Do(func() {
|
||||
d.features = d.Driver.Features(ctx)
|
||||
})
|
||||
return d.features
|
||||
func (d *DriverHandle) getClientOptions() ([]client.ClientOpt, error) {
|
||||
exp, _, err := detect.Exporter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if td, ok := exp.(client.TracerDelegate); ok {
|
||||
return []client.ClientOpt{
|
||||
client.WithTracerDelegate(td),
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (d *DriverHandle) HistoryAPISupported(ctx context.Context) bool {
|
||||
d.historyAPISupportedOnce.Do(func() {
|
||||
if c, err := d.Client(ctx); err == nil {
|
||||
d.historyAPISupported = historyAPISupported(ctx, c)
|
||||
}
|
||||
})
|
||||
return d.historyAPISupported
|
||||
}
|
||||
|
||||
@@ -2,17 +2,29 @@ package remote
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/buildx/driver"
|
||||
util "github.com/docker/buildx/driver/remote/util"
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/client/connhelper"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Driver struct {
|
||||
factory driver.Factory
|
||||
driver.InitConfig
|
||||
|
||||
// if you add fields, remember to update docs:
|
||||
// https://github.com/docker/docs/blob/main/content/build/drivers/remote.md
|
||||
*tlsOpts
|
||||
defaultLoad bool
|
||||
}
|
||||
|
||||
type tlsOpts struct {
|
||||
@@ -23,25 +35,15 @@ type tlsOpts struct {
|
||||
}
|
||||
|
||||
func (d *Driver) Bootstrap(ctx context.Context, l progress.Logger) error {
|
||||
for i := 0; ; i++ {
|
||||
info, err := d.Info(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.Status != driver.Inactive {
|
||||
return nil
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
if i > 10 {
|
||||
i = 10
|
||||
}
|
||||
time.Sleep(time.Duration(i) * time.Second)
|
||||
}
|
||||
c, err := d.Client(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return progress.Wrap("[internal] waiting for connection", l, func(_ progress.SubLogger) error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||
defer cancel()
|
||||
return c.Wait(ctx)
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
||||
@@ -75,34 +77,87 @@ func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||
opts := []client.ClientOpt{}
|
||||
if d.tlsOpts != nil {
|
||||
opts = append(opts, []client.ClientOpt{
|
||||
client.WithServerConfig(d.tlsOpts.serverName, d.tlsOpts.caCert),
|
||||
client.WithCredentials(d.tlsOpts.cert, d.tlsOpts.key),
|
||||
}...)
|
||||
func (d *Driver) Client(ctx context.Context, opts ...client.ClientOpt) (*client.Client, error) {
|
||||
opts = append([]client.ClientOpt{
|
||||
client.WithContextDialer(func(ctx context.Context, _ string) (net.Conn, error) {
|
||||
return d.Dial(ctx)
|
||||
}),
|
||||
}, opts...)
|
||||
return client.New(ctx, "", opts...)
|
||||
}
|
||||
|
||||
func (d *Driver) Dial(ctx context.Context) (net.Conn, error) {
|
||||
addr := d.InitConfig.EndpointAddr
|
||||
ch, err := connhelper.GetConnectionHelper(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ch != nil {
|
||||
return ch.ContextDialer(ctx, addr)
|
||||
}
|
||||
|
||||
return client.New(ctx, d.InitConfig.EndpointAddr, opts...)
|
||||
network, addr, ok := strings.Cut(addr, "://")
|
||||
if !ok {
|
||||
return nil, errors.Errorf("invalid endpoint address: %s", d.InitConfig.EndpointAddr)
|
||||
}
|
||||
|
||||
conn, err := util.DialContext(ctx, network, addr)
|
||||
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
if d.tlsOpts != nil {
|
||||
cfg, err := loadTLS(d.tlsOpts)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error loading tls config")
|
||||
}
|
||||
conn = tls.Client(conn, cfg)
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func loadTLS(opts *tlsOpts) (*tls.Config, error) {
|
||||
cfg := &tls.Config{
|
||||
ServerName: opts.serverName,
|
||||
RootCAs: x509.NewCertPool(),
|
||||
}
|
||||
|
||||
if opts.caCert != "" {
|
||||
ca, err := os.ReadFile(opts.caCert)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read ca certificate")
|
||||
}
|
||||
if ok := cfg.RootCAs.AppendCertsFromPEM(ca); !ok {
|
||||
return nil, errors.New("failed to append ca certs")
|
||||
}
|
||||
}
|
||||
|
||||
if opts.cert != "" || opts.key != "" {
|
||||
cert, err := tls.LoadX509KeyPair(opts.cert, opts.key)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not read certificate/key")
|
||||
}
|
||||
cfg.Certificates = append(cfg.Certificates, cert)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (d *Driver) Features(ctx context.Context) map[driver.Feature]bool {
|
||||
var historyAPI bool
|
||||
c, err := d.Client(ctx)
|
||||
if err == nil {
|
||||
historyAPI = driver.HistoryAPISupported(ctx, c)
|
||||
c.Close()
|
||||
}
|
||||
return map[driver.Feature]bool{
|
||||
driver.OCIExporter: true,
|
||||
driver.DockerExporter: true,
|
||||
driver.CacheExport: true,
|
||||
driver.MultiPlatform: true,
|
||||
driver.HistoryAPI: historyAPI,
|
||||
driver.DefaultLoad: d.defaultLoad,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Driver) HostGatewayIP(ctx context.Context) (net.IP, error) {
|
||||
return nil, errors.New("host-gateway is not supported by the remote driver")
|
||||
}
|
||||
|
||||
func (d *Driver) Factory() driver.Factory {
|
||||
return d.factory
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user