mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-09-12 22:09:08 +08:00
Compare commits
2024 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59582a88fc | ||
|
|
a3b1fae96d | ||
|
|
6a84f43fba | ||
|
|
cf68b5b878 | ||
|
|
3f1aaa68d5 | ||
|
|
f6830f3b86 | ||
|
|
f6e57cf5b5 | ||
|
|
b77648d5f8 | ||
|
|
171fcbeb69 | ||
|
|
370a5aa127 | ||
|
|
13653fb84d | ||
|
|
1b16594f4a | ||
|
|
3905e8cf06 | ||
|
|
177b95c972 | ||
|
|
74fdbb5e7f | ||
|
|
ac331d3569 | ||
|
|
07c9b45bae | ||
|
|
b91957444b | ||
|
|
46c44c58ae | ||
|
|
6aed54c35a | ||
|
|
126fe653c7 | ||
|
|
f0cbc95eaf | ||
|
|
1a0f9fa96c | ||
|
|
df7a3db947 | ||
|
|
d294232cb5 | ||
|
|
0a7f5c4d94 | ||
|
|
5777d980b5 | ||
|
|
46cf94092c | ||
|
|
da3435ed3a | ||
|
|
3e90cc4b84 | ||
|
|
6418669e75 | ||
|
|
188495aa93 | ||
|
|
54a5c1ff93 | ||
|
|
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 | ||
|
|
47e34f2684 | ||
|
|
3d981be4ad | ||
|
|
5d94b0fcc7 | ||
|
|
569c66fb62 | ||
|
|
93f7fbdd78 | ||
|
|
ea06685c11 | ||
|
|
eaba4fa9e6 | ||
|
|
99e3882e2a | ||
|
|
0a2f35970c | ||
|
|
ab5f5e4169 | ||
|
|
696770d29c | ||
|
|
b47b4e5957 | ||
|
|
9a125afba0 | ||
|
|
d34103b0d9 | ||
|
|
c820350b5e | ||
|
|
61a7854659 | ||
|
|
e859ebc12e | ||
|
|
ef997fd6d0 | ||
|
|
76c96347ff | ||
|
|
48d7dafbd5 | ||
|
|
d03e93f6f1 | ||
|
|
fcb7810a38 | ||
|
|
459d94bdf1 | ||
|
|
7cef021a8a | ||
|
|
c6db4cf342 | ||
|
|
6c9436fbd5 | ||
|
|
a906149930 | ||
|
|
af328fe413 | ||
|
|
183a73abae | ||
|
|
b7f0b3d763 | ||
|
|
5b27d5a9f6 | ||
|
|
8f24c58f4d | ||
|
|
cd1648192e | ||
|
|
8d822fb06c | ||
|
|
0758a9b75d | ||
|
|
f8fa526678 | ||
|
|
4abff3ce12 | ||
|
|
e7034f66bc | ||
|
|
8c65e4fc1d | ||
|
|
d196ac347e | ||
|
|
9b723ece46 | ||
|
|
5e2f8bd64a | ||
|
|
5788ab33d2 | ||
|
|
f1788002e1 | ||
|
|
6c62225d1b | ||
|
|
38b4eef5c6 | ||
|
|
a4db138c5e | ||
|
|
55377b2b0f | ||
|
|
98dedd3225 | ||
|
|
74b121be66 | ||
|
|
b9cf46785b | ||
|
|
ecf8dd0a26 | ||
|
|
73c17ef4d2 | ||
|
|
e762e46b4b | ||
|
|
cafeedba79 | ||
|
|
17bdbbd3c3 | ||
|
|
2dae553d18 | ||
|
|
91c17f25fb | ||
|
|
63fc01e08a | ||
|
|
354ccc9469 | ||
|
|
68ae67720a | ||
|
|
b273db20c3 | ||
|
|
0ae88ecc4d | ||
|
|
341fb65f6f | ||
|
|
69a9c6609a | ||
|
|
1c96fdaf03 | ||
|
|
c77bd8a578 | ||
|
|
e5f701351c | ||
|
|
09798cdebd | ||
|
|
0dfc35d558 | ||
|
|
8085f57a3a | ||
|
|
d582a21acd | ||
|
|
580820a4de | ||
|
|
b7e8afc61b | ||
|
|
a8a637e19d | ||
|
|
79632a4c4c | ||
|
|
a6b0959276 | ||
|
|
6d7142b057 | ||
|
|
d0bff18cee | ||
|
|
7e39644f69 | ||
|
|
adc6349b28 | ||
|
|
f558fd8b22 | ||
|
|
432e16ef70 | ||
|
|
8c86c2242a | ||
|
|
75ad5d732b | ||
|
|
9bd0202312 | ||
|
|
367f114cc7 | ||
|
|
2959ce205e | ||
|
|
75b5c6560f | ||
|
|
4429ccbcc2 | ||
|
|
c59fc18325 | ||
|
|
4ce80856b3 | ||
|
|
af3feec4ea | ||
|
|
90c849f5ef | ||
|
|
6024212ac8 | ||
|
|
2d124e0ce9 | ||
|
|
e61a8cf637 | ||
|
|
167cd16acb | ||
|
|
1dd31fefcb | ||
|
|
5a12b25bab | ||
|
|
b702188b65 | ||
|
|
060ac842bb | ||
|
|
31d1b778ff | ||
|
|
1cd4b54810 | ||
|
|
c54926c5b2 | ||
|
|
10aea8e970 | ||
|
|
be6542911f | ||
|
|
9b07f6510a | ||
|
|
9ee19520dd | ||
|
|
878faae332 | ||
|
|
eaf38570e7 | ||
|
|
167340df17 | ||
|
|
e61a1da7fc | ||
|
|
f8483d7243 | ||
|
|
2c8a9aad76 | ||
|
|
32009a701c | ||
|
|
0cbc316f76 | ||
|
|
45fccef3f3 | ||
|
|
fdcb4e2fb9 | ||
|
|
4a0a67d7a2 | ||
|
|
855d49ff58 | ||
|
|
384e873db0 | ||
|
|
60e72ba989 | ||
|
|
45a2ae6762 | ||
|
|
2eeef180ea | ||
|
|
8fd81f5cfd | ||
|
|
1eb9ad979e | ||
|
|
77e0e860f8 | ||
|
|
e228c398f4 | ||
|
|
5d06406f26 | ||
|
|
cb061b684c | ||
|
|
29b427ce13 | ||
|
|
4fa7cd1fc2 | ||
|
|
12b6a3ad9a | ||
|
|
22e1901581 | ||
|
|
e23c37fa96 | ||
|
|
e5a0ed1149 | ||
|
|
c9c1303e31 | ||
|
|
ae3299d9d4 | ||
|
|
a948cc14c5 | ||
|
|
621b07c799 | ||
|
|
7ad970f93a | ||
|
|
437fe55104 | ||
|
|
bebd244e33 | ||
|
|
9f2143e3df | ||
|
|
98efe7af10 | ||
|
|
c7c37c3591 | ||
|
|
a43837d26c | ||
|
|
f115abb509 | ||
|
|
43a07f3997 | ||
|
|
41e1693be0 | ||
|
|
9d5af461b2 | ||
|
|
b38c9c7db4 | ||
|
|
9f884edbbf | ||
|
|
0a7a2b1882 | ||
|
|
6bec8f6e00 | ||
|
|
65037e4611 | ||
|
|
ba92989a94 | ||
|
|
2bf996d9ad | ||
|
|
75ed3e296b | ||
|
|
e14e0521cf | ||
|
|
28e6995f7c | ||
|
|
8f72fb353c | ||
|
|
14f5d490ef | ||
|
|
c9095e8eab | ||
|
|
0589f69206 | ||
|
|
b724a173a9 | ||
|
|
e5ccb64617 | ||
|
|
08d114195f | ||
|
|
caf7d2ec9b | ||
|
|
2dffed3f3a | ||
|
|
784dc2223d | ||
|
|
c3fd1e8b79 | ||
|
|
6f0c550ee9 | ||
|
|
5d551dbbc1 | ||
|
|
043cb3a0db | ||
|
|
16d5b38f2b | ||
|
|
956a1be656 | ||
|
|
afcaa8df5f | ||
|
|
12885c01ad | ||
|
|
2ab8749052 | ||
|
|
e826141af4 | ||
|
|
0c1fd31226 | ||
|
|
0e9804901b | ||
|
|
2402607846 | ||
|
|
3d49bbd23a | ||
|
|
33b1fdbf39 | ||
|
|
de4cdab411 | ||
|
|
a7e471b7b3 | ||
|
|
ba6e5cddb0 | ||
|
|
e4ff82f864 | ||
|
|
48b733d6da | ||
|
|
0b432cc5f2 | ||
|
|
f6cccefffc | ||
|
|
fd5d90c699 | ||
|
|
06399630a2 | ||
|
|
20693aa808 | ||
|
|
f373b91cc3 | ||
|
|
ce48b1ae84 | ||
|
|
b3340cc7ba | ||
|
|
1303715aba | ||
|
|
b716e48926 | ||
|
|
7d35a3b8d8 | ||
|
|
200058b505 | ||
|
|
566f41b598 | ||
|
|
6c0547e7e6 | ||
|
|
871f865ac8 | ||
|
|
62a21520ea | ||
|
|
a597266a52 | ||
|
|
14b66817fb | ||
|
|
af011d6ca3 | ||
|
|
8a02cf8717 | ||
|
|
672eeed9a6 | ||
|
|
1b816ff838 | ||
|
|
10365ddf22 | ||
|
|
a28cb1491d | ||
|
|
1e149bb84f | ||
|
|
9827abbf76 | ||
|
|
a3293cdaaa | ||
|
|
f7d8bd2055 | ||
|
|
5d33a3af22 | ||
|
|
87f900ce77 | ||
|
|
bb5c93cafc | ||
|
|
c6ce0964b9 | ||
|
|
5c21e80a83 | ||
|
|
498cc9ba0a | ||
|
|
805f3a199d | ||
|
|
91fdb0423d | ||
|
|
8ba8659496 | ||
|
|
16e41ba297 | ||
|
|
387ce5be7c | ||
|
|
87a120e8e3 | ||
|
|
589d4e4cf5 | ||
|
|
6535f16aec | ||
|
|
a1520ea1b2 | ||
|
|
0844213897 | ||
|
|
989ba55d9a | ||
|
|
33388d6ede | ||
|
|
bfadbecb96 | ||
|
|
f815f4acf7 | ||
|
|
81d7decd13 | ||
|
|
d699d08399 | ||
|
|
9541457c54 | ||
|
|
c6cdcb02cf | ||
|
|
799715ea24 | ||
|
|
b5c6b3f10b | ||
|
|
3f59b27cf4 | ||
|
|
00b18558dd | ||
|
|
948414e1b2 | ||
|
|
56876ab825 | ||
|
|
0806870261 | ||
|
|
fd8eaab2df | ||
|
|
77252f161c | ||
|
|
4437802e63 | ||
|
|
1613fde55c | ||
|
|
624bc064d8 | ||
|
|
0c4a68555e | ||
|
|
476ac18d2c | ||
|
|
8ad30d0a35 | ||
|
|
780531425b | ||
|
|
92d2dc8263 | ||
|
|
cfa6b4f7c8 | ||
|
|
5d4223e4f8 | ||
|
|
4a73abfd64 | ||
|
|
6f722da04d | ||
|
|
527d57540e | ||
|
|
b65f49622e | ||
|
|
c5ce08bf3c | ||
|
|
71b35ae42e | ||
|
|
15eb6418e8 | ||
|
|
2a83723d57 | ||
|
|
e8f55a3cf7 | ||
|
|
b5ea989eee | ||
|
|
17105bfc50 | ||
|
|
eefe27ff42 | ||
|
|
1ea71e358a | ||
|
|
14d8f95ec9 | ||
|
|
b0728c96d3 | ||
|
|
5e685c0e04 | ||
|
|
f2ac30f431 | ||
|
|
6808c0e585 | ||
|
|
9de12bb9c8 | ||
|
|
0645acfd79 | ||
|
|
439d58ddbd | ||
|
|
c0a9274d64 | ||
|
|
f3a4cd5176 | ||
|
|
c2e11196dd | ||
|
|
0b8f0264b0 | ||
|
|
5c31d855fd | ||
|
|
90d7fb5e77 | ||
|
|
c4ad930e2a | ||
|
|
3d0c88695e | ||
|
|
7332140fdf | ||
|
|
132fababb0 | ||
|
|
71507c0b58 | ||
|
|
7888fdee58 | ||
|
|
fb61fde581 | ||
|
|
5258e44030 | ||
|
|
e16c1b289b | ||
|
|
376b73f078 | ||
|
|
1c6060f27d | ||
|
|
ed4fd965ff | ||
|
|
bc9cb2c66a | ||
|
|
aa05f4c207 | ||
|
|
62fbef22d0 | ||
|
|
2563685d27 | ||
|
|
598f1f0a62 | ||
|
|
8311b0963a | ||
|
|
b1949b7388 | ||
|
|
3341bd1740 | ||
|
|
74f64f88a7 | ||
|
|
d4a4aaf509 | ||
|
|
1f73f4fd5d | ||
|
|
77f83d4171 | ||
|
|
642f28f439 | ||
|
|
54f4dc8f6e | ||
|
|
89d99b1694 | ||
|
|
9753f63f57 | ||
|
|
04804ff355 | ||
|
|
ed9ea2476d | ||
|
|
d0d29168a5 | ||
|
|
abda257763 | ||
|
|
1b91bc2e02 | ||
|
|
56b9e785e5 | ||
|
|
081447c9b1 | ||
|
|
260117289b | ||
|
|
73dca749ca | ||
|
|
8ac380bfb3 | ||
|
|
aeac7e08f9 | ||
|
|
7c9cdc4353 | ||
|
|
67572785cf | ||
|
|
8a70e7634d | ||
|
|
6dd5589a9c | ||
|
|
78058ce5f3 | ||
|
|
fd5884189c | ||
|
|
ab7a9f008d | ||
|
|
a8eb2a7fbe | ||
|
|
fbb4f4dec8 | ||
|
|
46fd0a61ba | ||
|
|
6444c813dc | ||
|
|
dc8a2b0398 | ||
|
|
d9780e27cd | ||
|
|
ab44d03771 | ||
|
|
b53cb256e5 | ||
|
|
c3075923f4 | ||
|
|
a32881313b | ||
|
|
07548bc898 | ||
|
|
0e544fe835 | ||
|
|
21ac4c34fb | ||
|
|
d2fa4a5724 | ||
|
|
4bdf98cf20 | ||
|
|
5da09f0c23 | ||
|
|
48357ee0c6 | ||
|
|
6506166f02 | ||
|
|
5f130b25ad | ||
|
|
a9fd128910 | ||
|
|
cb94298a02 | ||
|
|
046084c0b8 | ||
|
|
18760253b9 | ||
|
|
ded6376ece | ||
|
|
a4d60a451d | ||
|
|
0f4030de5d | ||
|
|
f1a5a3ec50 | ||
|
|
87beaefbb8 | ||
|
|
451847183d | ||
|
|
7625a3a4b0 | ||
|
|
6db696748b | ||
|
|
14f9ae679d | ||
|
|
4789d2219c | ||
|
|
eacecf657c | ||
|
|
1de0be240f | ||
|
|
ea4bec2bad | ||
|
|
36d95bd3b9 | ||
|
|
c33b310b48 | ||
|
|
8af76c68a8 | ||
|
|
1f56f51740 | ||
|
|
49b3c0dba5 | ||
|
|
a718d07f64 | ||
|
|
f6da7ee135 | ||
|
|
7eb266de69 | ||
|
|
9f821dabeb | ||
|
|
a27b8395b1 | ||
|
|
b1b4e64c97 | ||
|
|
c1058c17aa | ||
|
|
059c347fc2 | ||
|
|
7145e021f9 | ||
|
|
9723f4f76c | ||
|
|
db72d0cc05 | ||
|
|
00b7d5b858 | ||
|
|
6cd0c11ab1 | ||
|
|
c1ab55a3f2 | ||
|
|
c756e3ba96 | ||
|
|
566f37b65b | ||
|
|
6d1ff27410 | ||
|
|
be55b41427 | ||
|
|
a4f01b41a4 | ||
|
|
01e1c28dd9 | ||
|
|
51e41b16db | ||
|
|
9e9cdc2e6d | ||
|
|
bc1d590ca7 | ||
|
|
900d9c294d | ||
|
|
65aac16139 | ||
|
|
4903f462f6 | ||
|
|
44b5a19c13 | ||
|
|
ba8fa6c403 | ||
|
|
5b3083e9e1 | ||
|
|
523a16aa35 | ||
|
|
43a748fd15 | ||
|
|
15a80b56b5 | ||
|
|
b14bfb9fa2 | ||
|
|
56950ece69 | ||
|
|
1d2ac78443 | ||
|
|
8b7aa1a168 | ||
|
|
1180d919f5 | ||
|
|
347417ee12 | ||
|
|
fb27e3f919 | ||
|
|
edb16f8aab | ||
|
|
5c56e947fe | ||
|
|
571871b084 | ||
|
|
8340c40647 | ||
|
|
9818055b0e | ||
|
|
484823c97d | ||
|
|
3ce17b01dc | ||
|
|
e68c566c1c | ||
|
|
19d16aa941 | ||
|
|
6852713121 | ||
|
|
c97500b117 | ||
|
|
85040a9067 | ||
|
|
b8285c17e6 | ||
|
|
332dfb4b92 | ||
|
|
cb279bb14b | ||
|
|
60c9cf74ce | ||
|
|
ff6754eb04 | ||
|
|
e6b9aba997 | ||
|
|
0302894bfb | ||
|
|
e46394c3be | ||
|
|
1885e41789 | ||
|
|
2fb9db994b | ||
|
|
287aaf1696 | ||
|
|
0e6f5a155e | ||
|
|
88852e2330 | ||
|
|
6369c50614 | ||
|
|
a22d0a35a4 | ||
|
|
c93c02df85 | ||
|
|
e584c6e1a7 | ||
|
|
64e4c19971 | ||
|
|
551b8f6785 | ||
|
|
fbbe1c1b91 | ||
|
|
1a85745bf1 | ||
|
|
0d1fea8134 | ||
|
|
19417e76e7 | ||
|
|
53d88a79ef | ||
|
|
4c21b7e680 | ||
|
|
a8f689c223 | ||
|
|
ba8e3f9bc5 | ||
|
|
477200d1f9 | ||
|
|
662738a7e5 | ||
|
|
f992b77535 | ||
|
|
21b2f135b5 | ||
|
|
71e6be5d99 | ||
|
|
df8e7d0a9a | ||
|
|
64422a48d9 | ||
|
|
04f9c62772 | ||
|
|
2185d07f05 | ||
|
|
a49d28e00e | ||
|
|
629128c497 | ||
|
|
70682b043e | ||
|
|
b741d26eb5 | ||
|
|
cf8fa4a404 | ||
|
|
fe76a1b179 | ||
|
|
df4957307f | ||
|
|
e21f56e801 | ||
|
|
e51b55e03c | ||
|
|
296b8249cb | ||
|
|
7c6b840199 | ||
|
|
2a6ff4cbfc | ||
|
|
6ad5e2fcf3 | ||
|
|
37811320ef | ||
|
|
99ac7f5f9e | ||
|
|
96aca741a2 | ||
|
|
12ec931237 | ||
|
|
0e293a4ec9 | ||
|
|
163712a23b | ||
|
|
5f4d463780 | ||
|
|
abc8121aa8 | ||
|
|
8c47277141 | ||
|
|
36b5cd18e8 | ||
|
|
1e72e32ec3 | ||
|
|
8e5e5a563d | ||
|
|
98049e7eda | ||
|
|
25aa893bad | ||
|
|
b270a20274 | ||
|
|
f0262dd10e | ||
|
|
f8b673eccd | ||
|
|
0c0c9a0030 | ||
|
|
d1f79317cf | ||
|
|
fa58522242 | ||
|
|
aa6fd3d888 | ||
|
|
ebdd8834a9 | ||
|
|
fe8d5627e0 | ||
|
|
b242e3280b | ||
|
|
cc01caaecb | ||
|
|
e7b5ee7518 | ||
|
|
63073b65c0 | ||
|
|
47cf72b8ba | ||
|
|
af24d72dd8 | ||
|
|
f451b455c4 | ||
|
|
16f4dfafb1 | ||
|
|
5b4e8b9d71 | ||
|
|
b06eaffeeb | ||
|
|
3d55540db1 | ||
|
|
3c2b9aab96 | ||
|
|
49d46e71de | ||
|
|
6c5168e1ec | ||
|
|
e91d5326fe | ||
|
|
48b573e835 | ||
|
|
4788eb24ab | ||
|
|
3ed2783f34 | ||
|
|
c0e8a41a6f | ||
|
|
23b217af24 | ||
|
|
3dab19f933 | ||
|
|
05efb6291f | ||
|
|
eba49fdefd | ||
|
|
29f2c49374 | ||
|
|
2245371696 | ||
|
|
74631d5808 | ||
|
|
9264b0ca09 | ||
|
|
a96fb92939 | ||
|
|
ae59e1f72e | ||
|
|
47167a4e6f | ||
|
|
23cabd67fb | ||
|
|
e66410b932 | ||
|
|
c3bba05770 | ||
|
|
69b91f2760 | ||
|
|
e6b09580b4 | ||
|
|
36e663edda | ||
|
|
60e2029e70 | ||
|
|
5e1db43e34 | ||
|
|
6e9b743296 | ||
|
|
ef9710d8e2 | ||
|
|
468b3b9c8c | ||
|
|
0d8c853917 | ||
|
|
df3b868fe7 | ||
|
|
3f6a5ab6ba | ||
|
|
aa1f4389b1 | ||
|
|
246cd2aee9 | ||
|
|
0b6f8149d1 | ||
|
|
4dda2ad58b | ||
|
|
15bb14fcf9 | ||
|
|
b68114375f | ||
|
|
83a09b3cf2 | ||
|
|
3690cb12e6 | ||
|
|
b4de4826c4 | ||
|
|
b06df637c7 | ||
|
|
9bb9ae43f9 | ||
|
|
35e7172b89 | ||
|
|
abebf4d955 | ||
|
|
1c826d253b | ||
|
|
d1b454232d | ||
|
|
be3b41acc6 | ||
|
|
2a3e51ebfe | ||
|
|
1382fda1c9 | ||
|
|
c658096c17 | ||
|
|
6097919958 | ||
|
|
330bdde0a3 | ||
|
|
a55404fa2e | ||
|
|
c8c7c9f376 | ||
|
|
df34c1ce45 | ||
|
|
da1d66c938 | ||
|
|
d32926a7e5 | ||
|
|
7f008a7d1e | ||
|
|
eab3f704f5 | ||
|
|
a50e89c38e | ||
|
|
85723a138f | ||
|
|
9c69ba6f6f | ||
|
|
e84ed65525 | ||
|
|
4060abd3aa | ||
|
|
c924a0428d | ||
|
|
33ef1b3a30 | ||
|
|
a6caf4b948 | ||
|
|
cc7e11da99 | ||
|
|
a4c3efe783 | ||
|
|
4e22846e95 | ||
|
|
ddbd0cd095 | ||
|
|
255a3ec82c | ||
|
|
167c77baec | ||
|
|
ca2718366e | ||
|
|
58d3a643b9 | ||
|
|
718b8085fa | ||
|
|
64930d7440 | ||
|
|
4d2f948869 | ||
|
|
19c224cbe1 | ||
|
|
efd1581c01 | ||
|
|
ac85f590ba | ||
|
|
b0d3162875 | ||
|
|
4715a7e9e1 | ||
|
|
c5aec243c9 | ||
|
|
c76f3d3dba | ||
|
|
7add6e48b6 | ||
|
|
1267e0c076 | ||
|
|
361c093a35 | ||
|
|
9ad39a29f7 | ||
|
|
f5a1d8bff9 | ||
|
|
8c86afbd57 | ||
|
|
4d6e36df99 | ||
|
|
f51884e893 | ||
|
|
4afd9ecf16 | ||
|
|
ed3b311de4 | ||
|
|
d030fcc076 | ||
|
|
398da1f916 | ||
|
|
3a5741f534 | ||
|
|
c53b0b8a12 | ||
|
|
8fd34669ed | ||
|
|
be7e91899b | ||
|
|
74a822568e | ||
|
|
105c214d15 | ||
|
|
2b6a51ed34 | ||
|
|
e98c252490 | ||
|
|
17f5d6309f | ||
|
|
6a46ea04ab | ||
|
|
7bd97f6717 | ||
|
|
2a9c98ae40 | ||
|
|
1adf80c613 | ||
|
|
f823d3c73c | ||
|
|
91f0ed3fc3 | ||
|
|
04b56c7331 | ||
|
|
3c1a20097f | ||
|
|
966c4d4e14 | ||
|
|
6b8289d68e | ||
|
|
294421db9c | ||
|
|
9fdf991c27 | ||
|
|
77b33260f8 | ||
|
|
33e5f47c6c | ||
|
|
25ceb90678 | ||
|
|
27e29055cb | ||
|
|
810ce31f4b | ||
|
|
e3c91c9d29 | ||
|
|
2f47838ea1 | ||
|
|
0566e62995 | ||
|
|
aeac42be47 | ||
|
|
aa21ff7efd | ||
|
|
57d22a7bd1 | ||
|
|
6804bcbf12 | ||
|
|
6d34cc0b60 | ||
|
|
1bb375fe5c | ||
|
|
ed00243a0c | ||
|
|
1223e759a4 | ||
|
|
4fd3ec1a50 | ||
|
|
7f9cad1e4e | ||
|
|
437b8b140f | ||
|
|
8f0d9bd71f | ||
|
|
1378c616d6 | ||
|
|
3b5dfb3fb4 | ||
|
|
9c22be5d9c | ||
|
|
42dea89247 | ||
|
|
982a332679 | ||
|
|
441853f189 | ||
|
|
611329fc7f | ||
|
|
f3c135e583 | ||
|
|
7f84582b37 | ||
|
|
297526c49d | ||
|
|
d01d394a2b | ||
|
|
17d4369866 | ||
|
|
fb5e1393a4 | ||
|
|
18dbde9ed6 | ||
|
|
2a13491919 | ||
|
|
3509a1a7ff | ||
|
|
da1f4b8496 | ||
|
|
5b2e1d3ce4 | ||
|
|
7d8a6bc1d7 | ||
|
|
a378f8095e | ||
|
|
005bc009e8 | ||
|
|
3bc7d4bec6 | ||
|
|
96c1b05238 | ||
|
|
98f9f806f3 | ||
|
|
c834ba1389 | ||
|
|
cab437adef | ||
|
|
eefa8188e1 | ||
|
|
1d8db8a738 | ||
|
|
75ddc5b811 | ||
|
|
17dc0e1108 | ||
|
|
64ac6c9621 | ||
|
|
a7753ea781 | ||
|
|
12a6eb5b22 | ||
|
|
74b21258b6 | ||
|
|
2f9d46ce27 | ||
|
|
7b660c4e30 | ||
|
|
406799eb1c | ||
|
|
ef0cbf20f4 | ||
|
|
7f572eb044 | ||
|
|
0defb614a4 | ||
|
|
18023d7f32 | ||
|
|
4983b98005 | ||
|
|
8675e02cea | ||
|
|
45fc3bf842 | ||
|
|
cf809aec47 | ||
|
|
cceb1acca8 | ||
|
|
e620c40a14 | ||
|
|
e1590bf68b | ||
|
|
bad07943b5 | ||
|
|
603595559f | ||
|
|
febcc25d1a | ||
|
|
e3c0e34b33 | ||
|
|
3f5974b7f9 | ||
|
|
7ab3dc080b | ||
|
|
0883beac30 | ||
|
|
f9102a3295 | ||
|
|
f360088ae7 | ||
|
|
dfc1b361a9 | ||
|
|
19641ec8ca | ||
|
|
02f7d54aed | ||
|
|
1f6612b118 | ||
|
|
c1fbebe73f | ||
|
|
30d650862d | ||
|
|
52fd555bdd | ||
|
|
7b25e2cffc | ||
|
|
5eb1e40fea | ||
|
|
7ef679d945 | ||
|
|
480bf2e123 | ||
|
|
0078390934 | ||
|
|
06c11ecb61 | ||
|
|
e27a5966ef | ||
|
|
f1a9f91323 | ||
|
|
4ecca34a42 | ||
|
|
37ca8631f9 | ||
|
|
d3412f1039 | ||
|
|
8288ce96cc | ||
|
|
0222b74ee1 | ||
|
|
97bccc5ecf | ||
|
|
47ea0c5b03 | ||
|
|
766653f7a6 | ||
|
|
264451ba18 | ||
|
|
a42eb73043 | ||
|
|
f2b504b77d | ||
|
|
68ef5b9c9b | ||
|
|
07992e66e0 | ||
|
|
4522331229 | ||
|
|
ec1ba14f3e | ||
|
|
0694efb566 | ||
|
|
1324827cd5 | ||
|
|
86825a95ce | ||
|
|
dd445e5f9b | ||
|
|
3075a5a8c1 | ||
|
|
9ff5fb0356 | ||
|
|
bc19deb5d0 | ||
|
|
1c7088ee42 | ||
|
|
97d3841fbf | ||
|
|
20022fd441 | ||
|
|
23455744ac | ||
|
|
0ee14fb653 | ||
|
|
ff57ae1705 | ||
|
|
8da133e34f | ||
|
|
b0deb8bdd7 | ||
|
|
6583dd3aa2 | ||
|
|
701c548e46 | ||
|
|
0db719af8a | ||
|
|
7eb1235629 | ||
|
|
11c1e03e93 | ||
|
|
bea1ac296c | ||
|
|
2df799d331 | ||
|
|
fecc6958cb | ||
|
|
02bae945c3 | ||
|
|
691723f9f9 | ||
|
|
900f356df9 | ||
|
|
724cb29042 | ||
|
|
f69c62f07a | ||
|
|
309c49413c | ||
|
|
6824cf4548 | ||
|
|
881b48a3b6 | ||
|
|
5b452b72a2 | ||
|
|
27fcb73c7c | ||
|
|
2aa22597f0 | ||
|
|
d9ef9bec34 | ||
|
|
3b4780ef19 | ||
|
|
12fde33d9b | ||
|
|
a0f92829a7 | ||
|
|
b438032a60 | ||
|
|
3cf549a7f7 | ||
|
|
f8884a58e9 | ||
|
|
5ce3909c48 | ||
|
|
45fac6dee3 | ||
|
|
a8bb25d1b5 | ||
|
|
387e1ecca6 | ||
|
|
ad7b077d13 | ||
|
|
432c2b2650 | ||
|
|
055e85f48f | ||
|
|
91fec23f5d | ||
|
|
0295555a5a | ||
|
|
6cb1b85d7b | ||
|
|
e0350f671a | ||
|
|
c1adfcb658 | ||
|
|
1343cdfc83 | ||
|
|
f40c2dbb86 | ||
|
|
50c23aa755 | ||
|
|
ff9517cbf0 | ||
|
|
824b0268d8 | ||
|
|
77ea999adb | ||
|
|
1807cfdd26 | ||
|
|
ebd7d062bf | ||
|
|
6cb026b766 | ||
|
|
1cb1ee018b | ||
|
|
71e4a39ae9 | ||
|
|
009730f5fd | ||
|
|
36466c0744 | ||
|
|
1406ff141b | ||
|
|
1eff9310f8 | ||
|
|
22ac3271d2 | ||
|
|
064bd92583 | ||
|
|
1beb3359a6 | ||
|
|
35f4268081 | ||
|
|
81ce766501 | ||
|
|
66a764f9c1 | ||
|
|
e4137b2eea | ||
|
|
48067735fc | ||
|
|
54a2a0c49f | ||
|
|
d611bbe609 | ||
|
|
1e71a3ffa7 | ||
|
|
4a215a943b | ||
|
|
69d95cc847 | ||
|
|
cdd391e556 | ||
|
|
d69fe6140d | ||
|
|
ca3507656d | ||
|
|
78ae826d74 | ||
|
|
5a8060ea9f | ||
|
|
908ce2d206 | ||
|
|
69824a5d27 | ||
|
|
5d38fff729 | ||
|
|
31d12c89fa | ||
|
|
8257a04a7d | ||
|
|
bdc41dd308 | ||
|
|
f6e00a609d | ||
|
|
1845edd647 | ||
|
|
cab4cfe28f | ||
|
|
815c1dd05c | ||
|
|
bbfdaa4161 | ||
|
|
a9e62dfa83 | ||
|
|
b9a408017c | ||
|
|
062cf29de2 | ||
|
|
a2f1de6459 | ||
|
|
98439f7f08 | ||
|
|
6854eec48d | ||
|
|
1edfb13ba8 | ||
|
|
35b238ee82 | ||
|
|
55b85f5bb2 | ||
|
|
57156ee95c | ||
|
|
c245f30a94 | ||
|
|
6e3babc461 | ||
|
|
4ee8b14f2a | ||
|
|
21b41e580a | ||
|
|
cc90c5ca3c | ||
|
|
519aca3672 | ||
|
|
43968ffa68 | ||
|
|
79ba92b7f8 | ||
|
|
e0cffbdbdf | ||
|
|
df799b6a0f | ||
|
|
27bdbea410 | ||
|
|
1e52c2107c | ||
|
|
cf298ee01c | ||
|
|
e9d6501a4f | ||
|
|
92009ed03c | ||
|
|
f2fc0e9eb5 | ||
|
|
38f1138a45 | ||
|
|
72758fef22 | ||
|
|
9cdd837f6b | ||
|
|
d7e4affe98 | ||
|
|
3dc83e5dd8 | ||
|
|
29f97f6762 | ||
|
|
88a45cfb24 | ||
|
|
03885ec9f1 | ||
|
|
a648d58f63 | ||
|
|
0b9d426175 | ||
|
|
1c23d1cef5 | ||
|
|
95086cf641 | ||
|
|
6a702ebe5b | ||
|
|
a6a1a362ad | ||
|
|
4a226568a0 | ||
|
|
a2d5bc7cca | ||
|
|
951201ac1b | ||
|
|
c0f8a8314b | ||
|
|
d64428cd2a | ||
|
|
3a90f99635 | ||
|
|
04b44b3a89 | ||
|
|
b7c4fe5a3a | ||
|
|
082c83b825 | ||
|
|
79de2c5d82 | ||
|
|
b8bcf1d810 | ||
|
|
28a4363672 | ||
|
|
1e98de491d | ||
|
|
b54a0aa37c | ||
|
|
e10c385167 | ||
|
|
add4301ed6 | ||
|
|
a60150cbc6 | ||
|
|
cad7ed68be | ||
|
|
c317ca1e95 | ||
|
|
3f6517747e | ||
|
|
adafbe0e65 | ||
|
|
a49ad031a5 | ||
|
|
c3db06cda0 | ||
|
|
1201782a11 | ||
|
|
243b428a58 | ||
|
|
785dc17f13 | ||
|
|
cad87f54c5 | ||
|
|
0b8dde1071 | ||
|
|
1ca30a58c2 | ||
|
|
1246e8da3a | ||
|
|
c0f31349a6 | ||
|
|
5c2d2f294d | ||
|
|
da4c27e9af | ||
|
|
111ea95629 | ||
|
|
3adca1c17d | ||
|
|
6ffe22b843 | ||
|
|
824cb42fe0 | ||
|
|
08bb626304 | ||
|
|
38311a35f2 | ||
|
|
fd62216cbc | ||
|
|
fc7ba75fd7 | ||
|
|
c8f7c1e93f | ||
|
|
b78c680207 | ||
|
|
d7412c9420 | ||
|
|
a7fba7bf3a | ||
|
|
19ff7cdadc | ||
|
|
c255c04eed | ||
|
|
9fcea76dea | ||
|
|
1416bc1d83 | ||
|
|
215a128fc1 | ||
|
|
4e4eea7814 | ||
|
|
8079bd2841 | ||
|
|
2d5368cccc | ||
|
|
a1256c6bb2 | ||
|
|
e7863eb664 | ||
|
|
171c4375a1 | ||
|
|
45844805ec | ||
|
|
b77d7864fa | ||
|
|
6efcee28d5 | ||
|
|
3ad24524c4 | ||
|
|
971b5d2b73 | ||
|
|
94c5dde85a | ||
|
|
f62c02329e | ||
|
|
d2e53f5e05 | ||
|
|
7af29802d4 | ||
|
|
6ac01ec9ac | ||
|
|
20a55e9184 | ||
|
|
6c56109083 | ||
|
|
dab3fe71bd | ||
|
|
9867ca279a | ||
|
|
91e550b715 | ||
|
|
280c008f81 | ||
|
|
5939a23af6 | ||
|
|
7f1041164e | ||
|
|
64ce211ba4 | ||
|
|
b5bf28d722 | ||
|
|
10debb577e | ||
|
|
75cdea48e4 | ||
|
|
d96d7fb2dc | ||
|
|
e3245a400a | ||
|
|
e871c39f05 | ||
|
|
9c0a23996d | ||
|
|
3b2aeb2d5b | ||
|
|
e98a476dc8 | ||
|
|
7677052cb7 | ||
|
|
c273e0986c | ||
|
|
9ee499ae27 | ||
|
|
230dfa96a3 | ||
|
|
f1a8f54c83 | ||
|
|
2bcf3524e5 | ||
|
|
26918513e3 | ||
|
|
893d505803 | ||
|
|
22aaa260e7 | ||
|
|
1bcc3556fc | ||
|
|
eef6deb7c2 | ||
|
|
542759ea31 | ||
|
|
e5f590a7fa | ||
|
|
ecf215b927 | ||
|
|
299fd19c49 | ||
|
|
4b633c3c7b | ||
|
|
eb8057e8e0 | ||
|
|
32f6358d78 | ||
|
|
3b47722032 | ||
|
|
e60f0f2c4f | ||
|
|
b39ebab666 | ||
|
|
f891187d8b | ||
|
|
307c94e5c7 | ||
|
|
60a025b227 | ||
|
|
fec415a8e0 | ||
|
|
2d7540fb0a | ||
|
|
595285736c | ||
|
|
378f0b45c6 | ||
|
|
c3dab802d8 | ||
|
|
fa04611afc | ||
|
|
ffa062dc95 | ||
|
|
0fc2b5ca85 | ||
|
|
9a1267cd02 | ||
|
|
c74b2fe7a4 | ||
|
|
6c69d970f7 | ||
|
|
d3e56ea9d9 | ||
|
|
11b771c789 | ||
|
|
278f94a8b6 | ||
|
|
14b38a9aa8 | ||
|
|
0044c28b1f | ||
|
|
b568b219bc | ||
|
|
aabbe5a56a | ||
|
|
b038dd063e | ||
|
|
f25d5ff02f | ||
|
|
b67bdedb23 | ||
|
|
3ccb883d95 | ||
|
|
785c861233 | ||
|
|
1b69919313 | ||
|
|
24db7366ba | ||
|
|
08547827db | ||
|
|
f37c253ae4 | ||
|
|
d77e2453da | ||
|
|
2b4d305c58 | ||
|
|
5d715ada96 | ||
|
|
3400fa5628 | ||
|
|
f04c8c8430 | ||
|
|
de6b04d726 | ||
|
|
fe9f9bba87 | ||
|
|
e482ba2c73 | ||
|
|
038727477c | ||
|
|
ed4103ef52 | ||
|
|
54286a0117 | ||
|
|
9c3be32bc9 | ||
|
|
59533bbb5c | ||
|
|
5f8600f098 | ||
|
|
d95ebef55c | ||
|
|
33c121df01 | ||
|
|
1dde00c4bc | ||
|
|
4466a24f9e | ||
|
|
ec9daba87e | ||
|
|
202e99695b | ||
|
|
7371dda7a2 | ||
|
|
62bdf4d85e | ||
|
|
7f8dbf890d | ||
|
|
bede4ab552 | ||
|
|
5c5125f30e | ||
|
|
e9cf2cbe32 | ||
|
|
4ee7f70400 | ||
|
|
0abda783bb | ||
|
|
aadd118883 | ||
|
|
9aff9301ce | ||
|
|
93d6b654ca | ||
|
|
42287815b5 | ||
|
|
dcabc22072 | ||
|
|
ae53101e89 | ||
|
|
61627c2ece | ||
|
|
ab73275f58 | ||
|
|
316ca972b6 | ||
|
|
5c2b9bbfc5 | ||
|
|
cc2a879660 | ||
|
|
89334a88a9 | ||
|
|
1927dba42f | ||
|
|
72dab552b5 | ||
|
|
a0a7db127c | ||
|
|
bcfd434829 | ||
|
|
d1aaed7a77 | ||
|
|
f0026081a7 | ||
|
|
a18829f837 | ||
|
|
da0eb138d0 | ||
|
|
a2c7d43e46 | ||
|
|
f7cba04f5e | ||
|
|
12b5db70e2 | ||
|
|
5c4e3fc860 | ||
|
|
eab0e6a8fe | ||
|
|
4c938c77ba | ||
|
|
1cca41b81a | ||
|
|
c62472121b | ||
|
|
88d0775692 | ||
|
|
8afc82b427 | ||
|
|
d311561a8b | ||
|
|
44e180b26e | ||
|
|
02d29e0af5 | ||
|
|
40121c671c | ||
|
|
4c1621cccd | ||
|
|
7f0e37531c | ||
|
|
82b212bddf | ||
|
|
aa52a5a699 | ||
|
|
49342dd54d | ||
|
|
3f716f00fa | ||
|
|
5e25191cb6 | ||
|
|
dd15969c93 | ||
|
|
81cf2064c4 | ||
|
|
b497587f21 | ||
|
|
2890209a11 | ||
|
|
4690e14c40 | ||
|
|
25d2f73858 | ||
|
|
36a37a624e | ||
|
|
e150d7bdd8 | ||
|
|
be2c8f71fe | ||
|
|
89f5c1ce51 | ||
|
|
b6474d43a9 | ||
|
|
2644d56a6d | ||
|
|
084b6c0a95 | ||
|
|
8e5595b7c7 | ||
|
|
22500c9929 | ||
|
|
050f4f9219 | ||
|
|
1a56de8e68 | ||
|
|
868610e0e9 | ||
|
|
b89e2f35df | ||
|
|
1b3068df7c | ||
|
|
461369748c | ||
|
|
d5908cdddf | ||
|
|
b5bc754bad | ||
|
|
dff7673afb | ||
|
|
3e2fde5639 | ||
|
|
7a7b73c043 | ||
|
|
e50c9ae7be | ||
|
|
9e62c9f074 | ||
|
|
c82dbafaee | ||
|
|
0e4d7aa7a9 | ||
|
|
c05a6eb2c1 | ||
|
|
eec1693f30 | ||
|
|
c643c2ca95 | ||
|
|
761e22e395 | ||
|
|
ef8c936b27 | ||
|
|
0cea838344 | ||
|
|
2b18a9b4a5 | ||
|
|
45e4550c36 | ||
|
|
6fc906532b | ||
|
|
06541ebd0f | ||
|
|
773fac9a73 | ||
|
|
7f0e05dfac | ||
|
|
e59aecf034 | ||
|
|
ac9a1612d2 | ||
|
|
c83812144c | ||
|
|
df521e4e96 | ||
|
|
00cb53d0ef | ||
|
|
6cfef7fa36 | ||
|
|
b05c313204 | ||
|
|
3e8bbbc286 | ||
|
|
8a12884814 | ||
|
|
6cf9fa8261 | ||
|
|
fd94fc5fdf | ||
|
|
45c678ad26 | ||
|
|
55a3ce606f | ||
|
|
c1c414e4c9 | ||
|
|
610601cec0 | ||
|
|
9833420a03 | ||
|
|
7f322caa79 | ||
|
|
93867d02f0 | ||
|
|
b8a602821c | ||
|
|
a8a3b1738e | ||
|
|
ef3e46fd62 | ||
|
|
3ab0b6953a | ||
|
|
c19c018a4c | ||
|
|
422ba60b04 | ||
|
|
2d3763990c | ||
|
|
dc6ada9b50 | ||
|
|
cb185f095f | ||
|
|
89e126fa60 | ||
|
|
04bac63745 | ||
|
|
3594851128 | ||
|
|
58e5a73389 | ||
|
|
c685e46609 | ||
|
|
e3283e6169 | ||
|
|
5d50bd7b43 | ||
|
|
3dfbe2c184 | ||
|
|
06367a120b | ||
|
|
6149507c7e | ||
|
|
c76b5eac03 | ||
|
|
cd133cee25 | ||
|
|
eeab638476 | ||
|
|
19b9b86af8 | ||
|
|
0101c96532 | ||
|
|
85dedf1aea | ||
|
|
5f05bd9a2b | ||
|
|
260d07a9a1 | ||
|
|
9aa8f09f14 | ||
|
|
0363b676bc | ||
|
|
a10045e8cb | ||
|
|
0afcca221d | ||
|
|
5daf176722 | ||
|
|
3d1ab82dc6 | ||
|
|
872430d2d3 | ||
|
|
7d312eaa0a | ||
|
|
a6bc4ed21e | ||
|
|
3768ab268b | ||
|
|
4c2daeb852 | ||
|
|
d9ee3b134c | ||
|
|
0b6ba1cd32 | ||
|
|
65a6955db8 | ||
|
|
258d12b2e7 | ||
|
|
6e3a319a9d | ||
|
|
1bb425a882 | ||
|
|
5f6ad50df4 | ||
|
|
9d88450118 | ||
|
|
334c93fbbe | ||
|
|
6ba080d337 | ||
|
|
ba443811e4 | ||
|
|
67bd6f4dc8 | ||
|
|
9f50eccbd7 | ||
|
|
12db50748b | ||
|
|
9b4937f062 | ||
|
|
3d48359e95 | ||
|
|
70002ebbc7 | ||
|
|
ef95f8135b | ||
|
|
9215fc56a3 | ||
|
|
1253020b3d | ||
|
|
621c55066c | ||
|
|
77632ac15f | ||
|
|
db6aa34252 | ||
|
|
7ecfd3d298 | ||
|
|
9a8c287629 | ||
|
|
591099a4b8 | ||
|
|
31309b9205 | ||
|
|
8c0cefcd89 | ||
|
|
a07f5cdf42 | ||
|
|
a1d899d400 | ||
|
|
886e1a378c | ||
|
|
47b7ba4e79 | ||
|
|
79433cef7a | ||
|
|
c5eb8f58b4 | ||
|
|
03b7128b60 | ||
|
|
15b358bec6 | ||
|
|
a53e392afb | ||
|
|
4fec647b9d | ||
|
|
d7b28fb4d3 | ||
|
|
9bc9291fc9 | ||
|
|
df7a318ec0 | ||
|
|
908a856079 | ||
|
|
8d64b6484f | ||
|
|
399df854ea | ||
|
|
328441cdc6 | ||
|
|
5ca0cbff8e | ||
|
|
ab09846df7 | ||
|
|
cd3a9ad38d | ||
|
|
adc5f35237 | ||
|
|
0b984e429b | ||
|
|
eec843a325 | ||
|
|
83868a48b7 | ||
|
|
98d337af21 | ||
|
|
b2c7dc00cc | ||
|
|
44ddc5a02b | ||
|
|
f036bba48c | ||
|
|
0fe2ce7fac | ||
|
|
0147b92230 | ||
|
|
4047bccf6c | ||
|
|
363c0fdf4b | ||
|
|
c46407b2d3 | ||
|
|
ca0f5dabea | ||
|
|
17d4106e1b | ||
|
|
442d38080e | ||
|
|
87ec3af5bb | ||
|
|
1a8af33ff6 | ||
|
|
ff749d8863 | ||
|
|
2d86ddd37f | ||
|
|
e1bbb9d8de | ||
|
|
d7964be29c | ||
|
|
3fef64f584 | ||
|
|
319b6503a5 | ||
|
|
d40a6082fa | ||
|
|
28809b82a2 | ||
|
|
c9f02c32d4 | ||
|
|
55d5b80dfe | ||
|
|
33f25acb08 | ||
|
|
0e9066f6ed | ||
|
|
7d2e30096b | ||
|
|
0e9d6460db | ||
|
|
927163bf13 | ||
|
|
8ac1cf6e45 | ||
|
|
dba79ba223 | ||
|
|
905be6431b | ||
|
|
ad95d6ba04 | ||
|
|
b77690a373 | ||
|
|
84a734dc87 | ||
|
|
5079b64ab5 | ||
|
|
6a343488d2 | ||
|
|
98c3ef60e6 | ||
|
|
73fa351b1c | ||
|
|
c88f7fc307 | ||
|
|
55b8712268 | ||
|
|
7878f0c514 | ||
|
|
0f09e2ecfe | ||
|
|
bea3acd4b6 | ||
|
|
fb9004d6b2 | ||
|
|
42b7e7bc56 | ||
|
|
4b2ddd5b6e | ||
|
|
b3006221f1 | ||
|
|
e57108e7c9 | ||
|
|
6b3dc6687b | ||
|
|
92f6f9f973 | ||
|
|
a56a4c00dd | ||
|
|
ee4a115d4c | ||
|
|
976a58c918 | ||
|
|
db82aa1b77 | ||
|
|
d05504c50f | ||
|
|
f1f464e364 | ||
|
|
57b875a955 | ||
|
|
ea5d32ddff | ||
|
|
da8c8ccaf5 | ||
|
|
dcbe4b3e1a | ||
|
|
68cebffe13 | ||
|
|
96e7f3224a | ||
|
|
f6d83c97bb | ||
|
|
74f76cf4e9 | ||
|
|
8b8725d1fd | ||
|
|
20494f799d | ||
|
|
dd13e16bc7 | ||
|
|
11057da373 | ||
|
|
381dc8fb43 | ||
|
|
780fad46f2 | ||
|
|
2ca5ffa06a | ||
|
|
f349ba8750 | ||
|
|
33e3ca524e | ||
|
|
ea1a71dc07 | ||
|
|
ae820293a2 | ||
|
|
f68f42cb11 | ||
|
|
7f58ad45fa | ||
|
|
aa7c17989b | ||
|
|
6b6afc4077 | ||
|
|
69a1419ab1 | ||
|
|
080e9981c7 | ||
|
|
8cc00ab486 | ||
|
|
40fad4bbb5 | ||
|
|
232af9aa0d | ||
|
|
5bf2ff98c9 | ||
|
|
570e733a51 | ||
|
|
cffcd57edb | ||
|
|
1496ac9b55 | ||
|
|
290e25917c | ||
|
|
0360668cc1 | ||
|
|
343a4753c7 | ||
|
|
d827f42d38 | ||
|
|
5843e67a90 | ||
|
|
517df133e3 | ||
|
|
621114fbe1 | ||
|
|
2066051d3a | ||
|
|
d94cbd870c | ||
|
|
48f15dcf3d | ||
|
|
35a60b8e04 | ||
|
|
4b3df09155 | ||
|
|
b1215c2ce2 | ||
|
|
99ac03f9f3 | ||
|
|
a0aa45a4a7 | ||
|
|
aab3a92890 | ||
|
|
37020dc8da | ||
|
|
d66d3a2d09 | ||
|
|
f057195a4f | ||
|
|
378bf70d4b | ||
|
|
1ccf0bd7d8 | ||
|
|
ddbfddce88 | ||
|
|
ea19cf9d8d | ||
|
|
3b69482a2f | ||
|
|
778fbb4669 | ||
|
|
13533e359a | ||
|
|
3c2d0aa667 | ||
|
|
5551de4b8a | ||
|
|
fa51b90094 | ||
|
|
bfd1ea3877 | ||
|
|
abfb2c064d | ||
|
|
4f7517115c | ||
|
|
1621b9bad0 | ||
|
|
d2bf42f8b4 | ||
|
|
d1a46faf84 | ||
|
|
39f1d99dcc | ||
|
|
1f04ec9575 | ||
|
|
ac2e081528 | ||
|
|
95ac9ebb8a | ||
|
|
c41b006be1 | ||
|
|
92fb995505 | ||
|
|
3c94621142 | ||
|
|
2d720a1e0b | ||
|
|
0269388aa7 | ||
|
|
4b2aab09b5 | ||
|
|
1c7434a8f0 | ||
|
|
20f8f67928 | ||
|
|
159535261d | ||
|
|
a840e891fe | ||
|
|
a7c704c39d | ||
|
|
e1c0eb2187 | ||
|
|
aa8ab9fcca | ||
|
|
a746959fc1 | ||
|
|
ee34eb2180 | ||
|
|
844b901005 | ||
|
|
83ebc13a37 | ||
|
|
82c8f2d8f0 | ||
|
|
a11cc8840e | ||
|
|
35ee6ce62d | ||
|
|
37861cb99f | ||
|
|
a178d05023 | ||
|
|
ee9ccfe2e3 | ||
|
|
c6c4b4a871 | ||
|
|
273c6a75a2 | ||
|
|
1384bf02f9 |
@@ -1,2 +1 @@
|
|||||||
bin/
|
bin/
|
||||||
cross-out/
|
|
||||||
|
|||||||
54
.github/CONTRIBUTING.md
vendored
54
.github/CONTRIBUTING.md
vendored
@@ -116,6 +116,60 @@ commit automatically with `git commit -s`.
|
|||||||
|
|
||||||
### Run the unit- and integration-tests
|
### Run the unit- and integration-tests
|
||||||
|
|
||||||
|
Running tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
This runs all unit and integration tests, in a containerized environment.
|
||||||
|
Locally, every package can be tested separately with standard Go tools, but
|
||||||
|
integration tests are skipped if local user doesn't have enough permissions or
|
||||||
|
worker binaries are not installed.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# run unit tests only
|
||||||
|
make test-unit
|
||||||
|
|
||||||
|
# run integration tests only
|
||||||
|
make test-integration
|
||||||
|
|
||||||
|
# test a specific package
|
||||||
|
TESTPKGS=./bake make test
|
||||||
|
|
||||||
|
# run all integration tests with a specific worker
|
||||||
|
TESTFLAGS="--run=//worker=remote -v" make test-integration
|
||||||
|
|
||||||
|
# run a specific integration test
|
||||||
|
TESTFLAGS="--run /TestBuild/worker=remote/ -v" make test-integration
|
||||||
|
|
||||||
|
# run a selection of integration tests using a regexp
|
||||||
|
TESTFLAGS="--run /TestBuild.*/worker=remote/ -v" make test-integration
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> Set `TEST_KEEP_CACHE=1` for the test framework to keep external dependant
|
||||||
|
> images in a docker volume if you are repeatedly calling `make test`. This
|
||||||
|
> helps to avoid rate limiting on the remote registry side.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> Set `TEST_DOCKERD=1` for the test framework to enable the docker workers,
|
||||||
|
> specifically the `docker` and `docker-container` drivers.
|
||||||
|
>
|
||||||
|
> The docker tests cannot be run in parallel, so require passing `--parallel=1`
|
||||||
|
> in `TESTFLAGS`.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> If you are working behind a proxy, you can set some of or all
|
||||||
|
> `HTTP_PROXY=http://ip:port`, `HTTPS_PROXY=http://ip:port`, `NO_PROXY=http://ip:port`
|
||||||
|
> for the test framework to specify the proxy build args.
|
||||||
|
|
||||||
|
|
||||||
|
### Run the helper commands
|
||||||
|
|
||||||
To enter a demo container environment and experiment, you may run:
|
To enter a demo container environment and experiment, you may run:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|||||||
124
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
124
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
|
||||||
|
name: Bug Report
|
||||||
|
description: Report a bug
|
||||||
|
labels:
|
||||||
|
- status/triage
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thank you for taking the time to report a bug!
|
||||||
|
If this is a security issue please report it to the [Docker Security team](mailto:security@docker.com).
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: Contributing guidelines
|
||||||
|
description: |
|
||||||
|
Please read the contributing guidelines before proceeding.
|
||||||
|
options:
|
||||||
|
- label: I've read the [contributing guidelines](https://github.com/docker/buildx/blob/master/.github/CONTRIBUTING.md) and wholeheartedly agree
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
attributes:
|
||||||
|
label: I've found a bug and checked that ...
|
||||||
|
description: |
|
||||||
|
Make sure that your request fulfills all of the following requirements.
|
||||||
|
If one requirement cannot be satisfied, explain in detail why.
|
||||||
|
options:
|
||||||
|
- label: ... the documentation does not mention anything about my problem
|
||||||
|
- label: ... there are no open or closed issues that are related to my problem
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: |
|
||||||
|
Please provide a brief description of the bug in 1-2 sentences.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Expected behaviour
|
||||||
|
description: |
|
||||||
|
Please describe precisely what you'd expect to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Actual behaviour
|
||||||
|
description: |
|
||||||
|
Please describe precisely what is actually happening.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
attributes:
|
||||||
|
label: Buildx version
|
||||||
|
description: |
|
||||||
|
Output of `docker buildx version` command.
|
||||||
|
Example: `github.com/docker/buildx v0.8.1 5fac64c2c49dae1320f2b51f1a899ca451935554`
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Docker info
|
||||||
|
description: |
|
||||||
|
Output of `docker info` command.
|
||||||
|
render: text
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Builders list
|
||||||
|
description: |
|
||||||
|
Output of `docker buildx ls` command.
|
||||||
|
render: text
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Configuration
|
||||||
|
description: >
|
||||||
|
Please provide a minimal Dockerfile, bake definition (if applicable) and
|
||||||
|
invoked commands to help reproducing your issue.
|
||||||
|
placeholder: |
|
||||||
|
```dockerfile
|
||||||
|
FROM alpine
|
||||||
|
echo hello
|
||||||
|
```
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
group "default" {
|
||||||
|
targets = ["app"]
|
||||||
|
}
|
||||||
|
target "app" {
|
||||||
|
dockerfile = "Dockerfile"
|
||||||
|
target = "build"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build .
|
||||||
|
$ docker buildx bake
|
||||||
|
```
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Build logs
|
||||||
|
description: |
|
||||||
|
Please provide logs output (and/or BuildKit logs if applicable).
|
||||||
|
render: text
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: Additional info
|
||||||
|
description: |
|
||||||
|
Please provide any additional information that could be useful.
|
||||||
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
12
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/configuring-issue-templates-for-your-repository#configuring-the-template-chooser
|
||||||
|
blank_issues_enabled: true
|
||||||
|
contact_links:
|
||||||
|
- name: Questions and Discussions
|
||||||
|
url: https://github.com/docker/buildx/discussions/new
|
||||||
|
about: Use Github Discussions to ask questions and/or open discussion topics.
|
||||||
|
- name: Command line reference
|
||||||
|
url: https://docs.docker.com/engine/reference/commandline/buildx/
|
||||||
|
about: Read the command line reference.
|
||||||
|
- name: Documentation
|
||||||
|
url: https://docs.docker.com/build/
|
||||||
|
about: Read the documentation.
|
||||||
15
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
15
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/syntax-for-githubs-form-schema
|
||||||
|
name: Feature request
|
||||||
|
description: Missing functionality? Come tell us about it!
|
||||||
|
labels:
|
||||||
|
- kind/enhancement
|
||||||
|
- status/triage
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: description
|
||||||
|
attributes:
|
||||||
|
label: Description
|
||||||
|
description: What is the feature you want to see?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
12
.github/SECURITY.md
vendored
Normal file
12
.github/SECURITY.md
vendored
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Reporting security issues
|
||||||
|
|
||||||
|
The project maintainers take security seriously. If you discover a security
|
||||||
|
issue, please bring it to their attention right away!
|
||||||
|
|
||||||
|
**Please _DO NOT_ file a public issue**, instead send your report privately to
|
||||||
|
[security@docker.com](mailto:security@docker.com).
|
||||||
|
|
||||||
|
Security reports are greatly appreciated, and we will publicly thank you for it.
|
||||||
|
We also like to send gifts—if you're into schwag, make sure to let
|
||||||
|
us know. We currently do not offer a paid security bounty program, but are not
|
||||||
|
ruling it out in the future.
|
||||||
15
.github/dependabot.yml
vendored
Normal file
15
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
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"
|
||||||
735
.github/releases.json
vendored
Normal file
735
.github/releases.json
vendored
Normal file
@@ -0,0 +1,735 @@
|
|||||||
|
{
|
||||||
|
"latest": {
|
||||||
|
"id": 90741208,
|
||||||
|
"tag_name": "v0.10.2",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.10.2",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v6.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v6.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v7.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v7.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-ppc64le.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-ppc64le.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-riscv64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-riscv64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-s390x.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-s390x.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.10.2": {
|
||||||
|
"id": 90741208,
|
||||||
|
"tag_name": "v0.10.2",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.10.2",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.darwin-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v6.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v6.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v7.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm-v7.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-ppc64le.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-ppc64le.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-riscv64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-riscv64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-s390x.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.linux-s390x.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/buildx-v0.10.2.windows-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.2/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.10.1": {
|
||||||
|
"id": 90346950,
|
||||||
|
"tag_name": "v0.10.1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.10.1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.darwin-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.darwin-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.darwin-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.darwin-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-arm-v6.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-arm-v6.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-arm-v7.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-arm-v7.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-ppc64le.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-ppc64le.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-riscv64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-riscv64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-s390x.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.linux-s390x.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.windows-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.windows-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.windows-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/buildx-v0.10.1.windows-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.1/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.10.0": {
|
||||||
|
"id": 88388110,
|
||||||
|
"tag_name": "v0.10.0",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.10.0",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.darwin-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.darwin-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.darwin-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.darwin-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-arm-v6.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-arm-v6.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-arm-v7.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-arm-v7.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-ppc64le.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-ppc64le.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-riscv64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-riscv64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-s390x.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.linux-s390x.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.windows-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.windows-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.windows-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/buildx-v0.10.0.windows-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.10.0-rc3": {
|
||||||
|
"id": 88191592,
|
||||||
|
"tag_name": "v0.10.0-rc3",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.10.0-rc3",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.darwin-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.darwin-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.darwin-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.darwin-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-arm-v6.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-arm-v6.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-arm-v7.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-arm-v7.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-ppc64le.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-ppc64le.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-riscv64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-riscv64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-s390x.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.linux-s390x.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.windows-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.windows-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.windows-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/buildx-v0.10.0-rc3.windows-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc3/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.10.0-rc2": {
|
||||||
|
"id": 86248476,
|
||||||
|
"tag_name": "v0.10.0-rc2",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.10.0-rc2",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.darwin-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.darwin-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.darwin-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.darwin-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-arm-v6.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-arm-v6.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-arm-v7.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-arm-v7.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-ppc64le.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-ppc64le.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-riscv64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-riscv64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-s390x.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.linux-s390x.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.windows-amd64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.windows-amd64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.windows-arm64.provenance.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/buildx-v0.10.0-rc2.windows-arm64.sbom.json",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc2/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.10.0-rc1": {
|
||||||
|
"id": 85963900,
|
||||||
|
"tag_name": "v0.10.0-rc1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.10.0-rc1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/buildx-v0.10.0-rc1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/buildx-v0.10.0-rc1.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/buildx-v0.10.0-rc1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/buildx-v0.10.0-rc1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/buildx-v0.10.0-rc1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/buildx-v0.10.0-rc1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/buildx-v0.10.0-rc1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/buildx-v0.10.0-rc1.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/buildx-v0.10.0-rc1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/buildx-v0.10.0-rc1.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/buildx-v0.10.0-rc1.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.10.0-rc1/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.9.1": {
|
||||||
|
"id": 74760068,
|
||||||
|
"tag_name": "v0.9.1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.9.1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/buildx-v0.9.1.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.1/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.9.0": {
|
||||||
|
"id": 74546589,
|
||||||
|
"tag_name": "v0.9.0",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.9.0",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/buildx-v0.9.0.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/buildx-v0.9.0.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/buildx-v0.9.0.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/buildx-v0.9.0.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/buildx-v0.9.0.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/buildx-v0.9.0.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/buildx-v0.9.0.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/buildx-v0.9.0.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/buildx-v0.9.0.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/buildx-v0.9.0.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/buildx-v0.9.0.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.9.0-rc2": {
|
||||||
|
"id": 74052235,
|
||||||
|
"tag_name": "v0.9.0-rc2",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.9.0-rc2",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/buildx-v0.9.0-rc2.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/buildx-v0.9.0-rc2.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/buildx-v0.9.0-rc2.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/buildx-v0.9.0-rc2.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/buildx-v0.9.0-rc2.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/buildx-v0.9.0-rc2.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/buildx-v0.9.0-rc2.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/buildx-v0.9.0-rc2.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/buildx-v0.9.0-rc2.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/buildx-v0.9.0-rc2.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/buildx-v0.9.0-rc2.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc2/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.9.0-rc1": {
|
||||||
|
"id": 73389692,
|
||||||
|
"tag_name": "v0.9.0-rc1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.9.0-rc1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/buildx-v0.9.0-rc1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/buildx-v0.9.0-rc1.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/buildx-v0.9.0-rc1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/buildx-v0.9.0-rc1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/buildx-v0.9.0-rc1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/buildx-v0.9.0-rc1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/buildx-v0.9.0-rc1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/buildx-v0.9.0-rc1.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/buildx-v0.9.0-rc1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/buildx-v0.9.0-rc1.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/buildx-v0.9.0-rc1.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.9.0-rc1/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.8.2": {
|
||||||
|
"id": 63479740,
|
||||||
|
"tag_name": "v0.8.2",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.8.2",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/buildx-v0.8.2.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.2/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.8.1": {
|
||||||
|
"id": 62289050,
|
||||||
|
"tag_name": "v0.8.1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.8.1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/buildx-v0.8.1.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.1/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.8.0": {
|
||||||
|
"id": 61423774,
|
||||||
|
"tag_name": "v0.8.0",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.8.0",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/buildx-v0.8.0.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/buildx-v0.8.0.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/buildx-v0.8.0.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/buildx-v0.8.0.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/buildx-v0.8.0.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/buildx-v0.8.0.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/buildx-v0.8.0.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/buildx-v0.8.0.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/buildx-v0.8.0.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/buildx-v0.8.0.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/buildx-v0.8.0.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.8.0-rc1": {
|
||||||
|
"id": 60513568,
|
||||||
|
"tag_name": "v0.8.0-rc1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.8.0-rc1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/buildx-v0.8.0-rc1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/buildx-v0.8.0-rc1.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/buildx-v0.8.0-rc1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/buildx-v0.8.0-rc1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/buildx-v0.8.0-rc1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/buildx-v0.8.0-rc1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/buildx-v0.8.0-rc1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/buildx-v0.8.0-rc1.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/buildx-v0.8.0-rc1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/buildx-v0.8.0-rc1.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/buildx-v0.8.0-rc1.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.8.0-rc1/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.7.1": {
|
||||||
|
"id": 54098347,
|
||||||
|
"tag_name": "v0.7.1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.7.1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/buildx-v0.7.1.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.1/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.7.0": {
|
||||||
|
"id": 53109422,
|
||||||
|
"tag_name": "v0.7.0",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.7.0",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/buildx-v0.7.0.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/buildx-v0.7.0.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/buildx-v0.7.0.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/buildx-v0.7.0.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/buildx-v0.7.0.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/buildx-v0.7.0.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/buildx-v0.7.0.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/buildx-v0.7.0.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/buildx-v0.7.0.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/buildx-v0.7.0.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/buildx-v0.7.0.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.7.0-rc1": {
|
||||||
|
"id": 52726324,
|
||||||
|
"tag_name": "v0.7.0-rc1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.7.0-rc1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/buildx-v0.7.0-rc1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/buildx-v0.7.0-rc1.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/buildx-v0.7.0-rc1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/buildx-v0.7.0-rc1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/buildx-v0.7.0-rc1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/buildx-v0.7.0-rc1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/buildx-v0.7.0-rc1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/buildx-v0.7.0-rc1.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/buildx-v0.7.0-rc1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/buildx-v0.7.0-rc1.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/buildx-v0.7.0-rc1.windows-arm64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.7.0-rc1/checksums.txt"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.6.3": {
|
||||||
|
"id": 48691641,
|
||||||
|
"tag_name": "v0.6.3",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.6.3",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.3/buildx-v0.6.3.windows-arm64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.6.2": {
|
||||||
|
"id": 48207405,
|
||||||
|
"tag_name": "v0.6.2",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.6.2",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.2/buildx-v0.6.2.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.2/buildx-v0.6.2.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.2/buildx-v0.6.2.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.2/buildx-v0.6.2.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.2/buildx-v0.6.2.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.2/buildx-v0.6.2.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.2/buildx-v0.6.2.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.2/buildx-v0.6.2.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.2/buildx-v0.6.2.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.2/buildx-v0.6.2.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.2/buildx-v0.6.2.windows-arm64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.6.1": {
|
||||||
|
"id": 47064772,
|
||||||
|
"tag_name": "v0.6.1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.6.1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.1/buildx-v0.6.1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.1/buildx-v0.6.1.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.1/buildx-v0.6.1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.1/buildx-v0.6.1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.1/buildx-v0.6.1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.1/buildx-v0.6.1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.1/buildx-v0.6.1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.1/buildx-v0.6.1.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.1/buildx-v0.6.1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.1/buildx-v0.6.1.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.1/buildx-v0.6.1.windows-arm64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.6.0": {
|
||||||
|
"id": 46343260,
|
||||||
|
"tag_name": "v0.6.0",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.6.0",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0/buildx-v0.6.0.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0/buildx-v0.6.0.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0/buildx-v0.6.0.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0/buildx-v0.6.0.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0/buildx-v0.6.0.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0/buildx-v0.6.0.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0/buildx-v0.6.0.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0/buildx-v0.6.0.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0/buildx-v0.6.0.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0/buildx-v0.6.0.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0/buildx-v0.6.0.windows-arm64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.6.0-rc1": {
|
||||||
|
"id": 46230351,
|
||||||
|
"tag_name": "v0.6.0-rc1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.6.0-rc1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0-rc1/buildx-v0.6.0-rc1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0-rc1/buildx-v0.6.0-rc1.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0-rc1/buildx-v0.6.0-rc1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0-rc1/buildx-v0.6.0-rc1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0-rc1/buildx-v0.6.0-rc1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0-rc1/buildx-v0.6.0-rc1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0-rc1/buildx-v0.6.0-rc1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0-rc1/buildx-v0.6.0-rc1.linux-riscv64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0-rc1/buildx-v0.6.0-rc1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0-rc1/buildx-v0.6.0-rc1.windows-amd64.exe",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.6.0-rc1/buildx-v0.6.0-rc1.windows-arm64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.5.1": {
|
||||||
|
"id": 35276550,
|
||||||
|
"tag_name": "v0.5.1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.5.1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.darwin-universal",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.windows-amd64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.5.0": {
|
||||||
|
"id": 35268960,
|
||||||
|
"tag_name": "v0.5.0",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.5.0",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0/buildx-v0.5.0.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0/buildx-v0.5.0.darwin-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0/buildx-v0.5.0.darwin-universal",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0/buildx-v0.5.0.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0/buildx-v0.5.0.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0/buildx-v0.5.0.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0/buildx-v0.5.0.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0/buildx-v0.5.0.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0/buildx-v0.5.0.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0/buildx-v0.5.0.windows-amd64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.5.0-rc1": {
|
||||||
|
"id": 35015334,
|
||||||
|
"tag_name": "v0.5.0-rc1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.5.0-rc1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0-rc1/buildx-v0.5.0-rc1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0-rc1/buildx-v0.5.0-rc1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0-rc1/buildx-v0.5.0-rc1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0-rc1/buildx-v0.5.0-rc1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0-rc1/buildx-v0.5.0-rc1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0-rc1/buildx-v0.5.0-rc1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0-rc1/buildx-v0.5.0-rc1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.5.0-rc1/buildx-v0.5.0-rc1.windows-amd64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.4.2": {
|
||||||
|
"id": 30007794,
|
||||||
|
"tag_name": "v0.4.2",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.4.2",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.2/buildx-v0.4.2.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.2/buildx-v0.4.2.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.2/buildx-v0.4.2.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.2/buildx-v0.4.2.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.2/buildx-v0.4.2.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.2/buildx-v0.4.2.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.2/buildx-v0.4.2.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.2/buildx-v0.4.2.windows-amd64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.4.1": {
|
||||||
|
"id": 26067509,
|
||||||
|
"tag_name": "v0.4.1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.4.1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.1/buildx-v0.4.1.windows-amd64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.4.0": {
|
||||||
|
"id": 26028174,
|
||||||
|
"tag_name": "v0.4.0",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.4.0",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.0/buildx-v0.4.0.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.0/buildx-v0.4.0.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.0/buildx-v0.4.0.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.0/buildx-v0.4.0.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.0/buildx-v0.4.0.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.0/buildx-v0.4.0.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.0/buildx-v0.4.0.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.4.0/buildx-v0.4.0.windows-amd64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.3.1": {
|
||||||
|
"id": 20316235,
|
||||||
|
"tag_name": "v0.3.1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.3.1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.1/buildx-v0.3.1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.1/buildx-v0.3.1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.1/buildx-v0.3.1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.1/buildx-v0.3.1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.1/buildx-v0.3.1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.1/buildx-v0.3.1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.1/buildx-v0.3.1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.1/buildx-v0.3.1.windows-amd64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.3.0": {
|
||||||
|
"id": 19029664,
|
||||||
|
"tag_name": "v0.3.0",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.3.0",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.0/buildx-v0.3.0.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.0/buildx-v0.3.0.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.0/buildx-v0.3.0.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.0/buildx-v0.3.0.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.0/buildx-v0.3.0.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.0/buildx-v0.3.0.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.0/buildx-v0.3.0.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.3.0/buildx-v0.3.0.windows-amd64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.2.2": {
|
||||||
|
"id": 17671545,
|
||||||
|
"tag_name": "v0.2.2",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.2.2",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.2/buildx-v0.2.2.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.2/buildx-v0.2.2.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.2/buildx-v0.2.2.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.2/buildx-v0.2.2.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.2/buildx-v0.2.2.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.2/buildx-v0.2.2.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.2/buildx-v0.2.2.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.2/buildx-v0.2.2.windows-amd64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.2.1": {
|
||||||
|
"id": 17582885,
|
||||||
|
"tag_name": "v0.2.1",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.2.1",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.1/buildx-v0.2.1.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.1/buildx-v0.2.1.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.1/buildx-v0.2.1.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.1/buildx-v0.2.1.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.1/buildx-v0.2.1.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.1/buildx-v0.2.1.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.1/buildx-v0.2.1.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.1/buildx-v0.2.1.windows-amd64.exe"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"v0.2.0": {
|
||||||
|
"id": 16965310,
|
||||||
|
"tag_name": "v0.2.0",
|
||||||
|
"html_url": "https://github.com/docker/buildx/releases/tag/v0.2.0",
|
||||||
|
"assets": [
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.0/buildx-v0.2.0.darwin-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.0/buildx-v0.2.0.linux-amd64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.0/buildx-v0.2.0.linux-arm-v6",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.0/buildx-v0.2.0.linux-arm-v7",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.0/buildx-v0.2.0.linux-arm64",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.0/buildx-v0.2.0.linux-ppc64le",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.0/buildx-v0.2.0.linux-s390x",
|
||||||
|
"https://github.com/docker/buildx/releases/download/v0.2.0/buildx-v0.2.0.windows-amd64.exe"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
364
.github/workflows/build.yml
vendored
Normal file
364
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,364 @@
|
|||||||
|
name: build
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v[0-9]*'
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/releases.json'
|
||||||
|
- 'README.md'
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
|
env:
|
||||||
|
BUILDX_VERSION: "latest"
|
||||||
|
BUILDKIT_IMAGE: "moby/buildkit:latest"
|
||||||
|
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:
|
||||||
|
test-integration:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
env:
|
||||||
|
TESTFLAGS_DOCKER: "-v --parallel=1 --timeout=30m"
|
||||||
|
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-container
|
||||||
|
- remote
|
||||||
|
pkg:
|
||||||
|
- ./tests
|
||||||
|
mode:
|
||||||
|
- ""
|
||||||
|
- experimental
|
||||||
|
include:
|
||||||
|
- 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@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
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@v4
|
||||||
|
with:
|
||||||
|
targets: integration-test
|
||||||
|
set: |
|
||||||
|
*.output=type=docker,name=${{ env.TEST_IMAGE_ID }}
|
||||||
|
-
|
||||||
|
name: Test
|
||||||
|
run: |
|
||||||
|
./hack/test
|
||||||
|
env:
|
||||||
|
TEST_REPORT_SUFFIX: "-${{ env.TESTREPORTS_NAME }}"
|
||||||
|
TESTPKGS: "${{ matrix.pkg }}"
|
||||||
|
-
|
||||||
|
name: Send to Codecov
|
||||||
|
if: always()
|
||||||
|
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@fa6141aedf23596fb8bdcceab9cce8dadaa31bd9
|
||||||
|
with:
|
||||||
|
directory: ./bin/testreports
|
||||||
|
-
|
||||||
|
name: Upload test reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
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:
|
||||||
|
matrix: ${{ steps.platforms.outputs.matrix }}
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Create matrix
|
||||||
|
id: platforms
|
||||||
|
run: |
|
||||||
|
echo "matrix=$(docker buildx bake binaries-cross --print | jq -cr '.target."binaries-cross".platforms')" >>${GITHUB_OUTPUT}
|
||||||
|
-
|
||||||
|
name: Show matrix
|
||||||
|
run: |
|
||||||
|
echo ${{ steps.platforms.outputs.matrix }}
|
||||||
|
|
||||||
|
binaries:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs:
|
||||||
|
- prepare-binaries
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform: ${{ fromJson(needs.prepare-binaries.outputs.matrix) }}
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Prepare
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
version: ${{ env.BUILDX_VERSION }}
|
||||||
|
driver-opts: image=${{ env.BUILDKIT_IMAGE }}
|
||||||
|
buildkitd-flags: --debug
|
||||||
|
-
|
||||||
|
name: Build
|
||||||
|
run: |
|
||||||
|
make release
|
||||||
|
env:
|
||||||
|
PLATFORMS: ${{ matrix.platform }}
|
||||||
|
CACHE_FROM: type=gha,scope=binaries-${{ env.PLATFORM_PAIR }}
|
||||||
|
CACHE_TO: type=gha,scope=binaries-${{ env.PLATFORM_PAIR }},mode=max
|
||||||
|
-
|
||||||
|
name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: buildx-${{ env.PLATFORM_PAIR }}
|
||||||
|
path: ${{ env.DESTDIR }}/*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
bin-image:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs:
|
||||||
|
- test-integration
|
||||||
|
- test-unit
|
||||||
|
if: ${{ github.event_name != 'pull_request' && github.repository == 'docker/buildx' }}
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
version: ${{ env.BUILDX_VERSION }}
|
||||||
|
driver-opts: image=${{ env.BUILDKIT_IMAGE }}
|
||||||
|
buildkitd-flags: --debug
|
||||||
|
-
|
||||||
|
name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
${{ env.REPO_SLUG }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
bake-target: meta-helper
|
||||||
|
-
|
||||||
|
name: Login to DockerHub
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ vars.DOCKERPUBLICBOT_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERPUBLICBOT_WRITE_PAT }}
|
||||||
|
-
|
||||||
|
name: Build and push image
|
||||||
|
uses: docker/bake-action@v4
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
./docker-bake.hcl
|
||||||
|
${{ steps.meta.outputs.bake-file }}
|
||||||
|
targets: image-cross
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
sbom: true
|
||||||
|
set: |
|
||||||
|
*.cache-from=type=gha,scope=bin-image
|
||||||
|
*.cache-to=type=gha,scope=bin-image,mode=max
|
||||||
|
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs:
|
||||||
|
- test-integration
|
||||||
|
- test-unit
|
||||||
|
- binaries
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Download binaries
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: ${{ env.DESTDIR }}
|
||||||
|
pattern: buildx-*
|
||||||
|
merge-multiple: true
|
||||||
|
-
|
||||||
|
name: Create checksums
|
||||||
|
run: ./hack/hash-files
|
||||||
|
-
|
||||||
|
name: List artifacts
|
||||||
|
run: |
|
||||||
|
tree -nh ${{ env.DESTDIR }}
|
||||||
|
-
|
||||||
|
name: Check artifacts
|
||||||
|
run: |
|
||||||
|
find ${{ env.DESTDIR }} -type f -exec file -e ascii -- {} +
|
||||||
|
-
|
||||||
|
name: GitHub Release
|
||||||
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
|
uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564 # v2.0.4
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
files: ${{ env.DESTDIR }}/*
|
||||||
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"
|
||||||
71
.github/workflows/docs-release.yml
vendored
Normal file
71
.github/workflows/docs-release.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
name: docs-release
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
tag:
|
||||||
|
description: 'Git tag'
|
||||||
|
required: true
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- released
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
open-pr:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
if: ${{ (github.event.release.prerelease != true || github.event.inputs.tag != '') && github.repository == 'docker/buildx' }}
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout docs repo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||||
|
repository: docker/docs
|
||||||
|
ref: main
|
||||||
|
-
|
||||||
|
name: Prepare
|
||||||
|
run: |
|
||||||
|
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@v3
|
||||||
|
-
|
||||||
|
name: Generate yaml
|
||||||
|
uses: docker/bake-action@v4
|
||||||
|
with:
|
||||||
|
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 yaml
|
||||||
|
run: |
|
||||||
|
cp /tmp/buildx-docs/out/reference/*.yaml ./data/buildx/
|
||||||
|
-
|
||||||
|
name: Update vendor
|
||||||
|
run: |
|
||||||
|
make vendor
|
||||||
|
env:
|
||||||
|
VENDOR_MODULE: github.com/docker/buildx@${{ env.RELEASE_NAME }}
|
||||||
|
-
|
||||||
|
name: Create PR on docs repo
|
||||||
|
uses: peter-evans/create-pull-request@c55203cfde3e5c11a452d352b4393e68b85b4533 # v6.0.3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||||
|
push-to-fork: docker-tools-robot/docker.github.io
|
||||||
|
commit-message: "vendor: github.com/docker/buildx ${{ env.RELEASE_NAME }}"
|
||||||
|
signoff: true
|
||||||
|
branch: dispatch/buildx-ref-${{ env.RELEASE_NAME }}
|
||||||
|
delete-branch: true
|
||||||
|
title: Update buildx reference to ${{ env.RELEASE_NAME }}
|
||||||
|
body: |
|
||||||
|
Update the buildx reference documentation to keep in sync with the latest release `${{ env.RELEASE_NAME }}`
|
||||||
|
draft: false
|
||||||
63
.github/workflows/docs-upstream.yml
vendored
Normal file
63
.github/workflows/docs-upstream.yml
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# this workflow runs the remote validate bake target from docker/docker.github.io
|
||||||
|
# to check if yaml reference docs and markdown files used in this repo are still valid
|
||||||
|
# https://github.com/docker/docker.github.io/blob/98c7c9535063ae4cd2cd0a31478a21d16d2f07a3/docker-bake.hcl#L34-L36
|
||||||
|
name: docs-upstream
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v[0-9]*'
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/docs-upstream.yml'
|
||||||
|
- 'docs/**'
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- '.github/workflows/docs-upstream.yml'
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docs-yaml:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
-
|
||||||
|
name: Build reference YAML docs
|
||||||
|
uses: docker/bake-action@v4
|
||||||
|
with:
|
||||||
|
targets: update-docs
|
||||||
|
provenance: false
|
||||||
|
set: |
|
||||||
|
*.output=/tmp/buildx-docs
|
||||||
|
*.cache-from=type=gha,scope=docs-yaml
|
||||||
|
*.cache-to=type=gha,scope=docs-yaml,mode=max
|
||||||
|
env:
|
||||||
|
DOCS_FORMATS: yaml
|
||||||
|
-
|
||||||
|
name: Upload reference YAML docs
|
||||||
|
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@6b73b05acb21edf7995cc5b3c6672d8e314cee7a # pin for artifact v4 support: https://github.com/docker/docs/pull/19220
|
||||||
|
needs:
|
||||||
|
- docs-yaml
|
||||||
|
with:
|
||||||
|
module-name: docker/buildx
|
||||||
|
data-files-id: docs-yaml
|
||||||
|
data-files-folder: buildx
|
||||||
|
create-placeholder-stubs: true
|
||||||
163
.github/workflows/e2e.yml
vendored
Normal file
163
.github/workflows/e2e.yml
vendored
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
name: e2e
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v[0-9]*'
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/releases.json'
|
||||||
|
- 'README.md'
|
||||||
|
- 'docs/**'
|
||||||
|
|
||||||
|
env:
|
||||||
|
DESTDIR: "./bin"
|
||||||
|
K3S_VERSION: "v1.21.2-k3s1"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
-
|
||||||
|
name: Build
|
||||||
|
uses: docker/bake-action@v4
|
||||||
|
with:
|
||||||
|
targets: binaries
|
||||||
|
set: |
|
||||||
|
*.cache-from=type=gha,scope=release
|
||||||
|
*.cache-from=type=gha,scope=binaries
|
||||||
|
*.cache-to=type=gha,scope=binaries
|
||||||
|
-
|
||||||
|
name: Rename binary
|
||||||
|
run: |
|
||||||
|
mv ${{ env.DESTDIR }}/build/buildx ${{ env.DESTDIR }}/build/docker-buildx
|
||||||
|
-
|
||||||
|
name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary
|
||||||
|
path: ${{ env.DESTDIR }}/build
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
driver:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
needs:
|
||||||
|
- build
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
driver:
|
||||||
|
- docker
|
||||||
|
- docker-container
|
||||||
|
- kubernetes
|
||||||
|
- remote
|
||||||
|
buildkit:
|
||||||
|
- moby/buildkit:buildx-stable-1
|
||||||
|
- moby/buildkit:master
|
||||||
|
buildkit-cfg:
|
||||||
|
- bkcfg-false
|
||||||
|
- bkcfg-true
|
||||||
|
multi-node:
|
||||||
|
- mnode-false
|
||||||
|
- mnode-true
|
||||||
|
platforms:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/amd64,linux/arm64
|
||||||
|
include:
|
||||||
|
- driver: kubernetes
|
||||||
|
driver-opt: qemu.install=true
|
||||||
|
- driver: remote
|
||||||
|
endpoint: tcp://localhost:1234
|
||||||
|
- driver: docker-container
|
||||||
|
metadata-provenance: max
|
||||||
|
exclude:
|
||||||
|
- driver: docker
|
||||||
|
multi-node: mnode-true
|
||||||
|
- driver: docker
|
||||||
|
buildkit-cfg: bkcfg-true
|
||||||
|
- driver: docker-container
|
||||||
|
multi-node: mnode-true
|
||||||
|
- driver: remote
|
||||||
|
multi-node: mnode-true
|
||||||
|
- driver: remote
|
||||||
|
buildkit-cfg: bkcfg-true
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
if: matrix.driver == 'docker' || matrix.driver == 'docker-container'
|
||||||
|
-
|
||||||
|
name: Install buildx
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: binary
|
||||||
|
path: /home/runner/.docker/cli-plugins
|
||||||
|
-
|
||||||
|
name: Fix perms and check
|
||||||
|
run: |
|
||||||
|
chmod +x /home/runner/.docker/cli-plugins/docker-buildx
|
||||||
|
docker buildx version
|
||||||
|
-
|
||||||
|
name: Init env vars
|
||||||
|
run: |
|
||||||
|
# BuildKit cfg
|
||||||
|
if [ "${{ matrix.buildkit-cfg }}" = "bkcfg-true" ]; then
|
||||||
|
cat > "/tmp/buildkitd.toml" <<EOL
|
||||||
|
[worker.oci]
|
||||||
|
max-parallelism = 2
|
||||||
|
EOL
|
||||||
|
echo "BUILDKIT_CFG=/tmp/buildkitd.toml" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
# Multi node
|
||||||
|
if [ "${{ matrix.multi-node }}" = "mnode-true" ]; then
|
||||||
|
echo "MULTI_NODE=1" >> $GITHUB_ENV
|
||||||
|
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: crazy-max/.github/.github/actions/install-k3s@fa6141aedf23596fb8bdcceab9cce8dadaa31bd9
|
||||||
|
with:
|
||||||
|
version: ${{ env.K3S_VERSION }}
|
||||||
|
-
|
||||||
|
name: Launch remote buildkitd
|
||||||
|
if: matrix.driver == 'remote'
|
||||||
|
run: |
|
||||||
|
docker run -d \
|
||||||
|
--privileged \
|
||||||
|
--name=remote-buildkit \
|
||||||
|
-p 1234:1234 \
|
||||||
|
${{ matrix.buildkit }} \
|
||||||
|
--addr unix:///run/buildkit/buildkitd.sock \
|
||||||
|
--addr tcp://0.0.0.0:1234
|
||||||
|
-
|
||||||
|
name: Test
|
||||||
|
run: |
|
||||||
|
make test-driver
|
||||||
|
env:
|
||||||
|
BUILDKIT_IMAGE: ${{ matrix.buildkit }}
|
||||||
|
DRIVER: ${{ matrix.driver }}
|
||||||
|
DRIVER_OPT: ${{ matrix.driver-opt }}
|
||||||
|
ENDPOINT: ${{ matrix.endpoint }}
|
||||||
|
PLATFORMS: ${{ matrix.platforms }}
|
||||||
48
.github/workflows/validate.yml
vendored
Normal file
48
.github/workflows/validate.yml
vendored
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
name: validate
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v[0-9]*'
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- '.github/releases.json'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
target:
|
||||||
|
- lint
|
||||||
|
- validate-vendor
|
||||||
|
- 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@v4
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
-
|
||||||
|
name: Run
|
||||||
|
run: |
|
||||||
|
make ${{ matrix.target }}
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1 @@
|
|||||||
bin
|
/bin
|
||||||
cross-out
|
|
||||||
|
|||||||
69
.golangci.yml
Normal file
69
.golangci.yml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
run:
|
||||||
|
timeout: 30m
|
||||||
|
skip-files:
|
||||||
|
- ".*\\.pb\\.go$"
|
||||||
|
|
||||||
|
modules-download-mode: vendor
|
||||||
|
|
||||||
|
build-tags:
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- gofmt
|
||||||
|
- govet
|
||||||
|
- depguard
|
||||||
|
- goimports
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- unused
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
- typecheck
|
||||||
|
- nolintlint
|
||||||
|
- gosec
|
||||||
|
- forbidigo
|
||||||
|
disable-all: true
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
depguard:
|
||||||
|
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)?$'
|
||||||
|
gosec:
|
||||||
|
excludes:
|
||||||
|
- G204 # Audit use of command execution
|
||||||
|
- G402 # TLS MinVersion too low
|
||||||
|
config:
|
||||||
|
G306: "0644"
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- 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
|
||||||
9
.mailmap
9
.mailmap
@@ -1,6 +1,13 @@
|
|||||||
# This file lists all individuals having contributed content to the repository.
|
# This file lists all individuals having contributed content to the repository.
|
||||||
# For how it is generated, see `hack/generate-authors`.
|
# For how it is generated, see hack/dockerfiles/authors.Dockerfile.
|
||||||
|
|
||||||
|
CrazyMax <github@crazymax.dev>
|
||||||
|
CrazyMax <github@crazymax.dev> <1951866+crazy-max@users.noreply.github.com>
|
||||||
|
CrazyMax <github@crazymax.dev> <crazy-max@users.noreply.github.com>
|
||||||
|
Sebastiaan van Stijn <github@gone.nl>
|
||||||
|
Sebastiaan van Stijn <github@gone.nl> <thaJeztah@users.noreply.github.com>
|
||||||
Tibor Vass <tibor@docker.com>
|
Tibor Vass <tibor@docker.com>
|
||||||
Tibor Vass <tibor@docker.com> <tiborvass@users.noreply.github.com>
|
Tibor Vass <tibor@docker.com> <tiborvass@users.noreply.github.com>
|
||||||
Tõnis Tiigi <tonistiigi@gmail.com>
|
Tõnis Tiigi <tonistiigi@gmail.com>
|
||||||
|
Ulysses Souza <ulyssessouza@gmail.com>
|
||||||
|
Wang Jinglei <morlay.null@gmail.com>
|
||||||
|
|||||||
35
.travis.yml
35
.travis.yml
@@ -1,35 +0,0 @@
|
|||||||
dist: trusty
|
|
||||||
sudo: required
|
|
||||||
|
|
||||||
install:
|
|
||||||
- docker run --name buildkit --rm -d --privileged -p 1234:1234 $REPO_SLUG_ORIGIN --addr tcp://0.0.0.0:1234
|
|
||||||
- sudo docker cp buildkit:/usr/bin/buildctl /usr/bin/
|
|
||||||
- export BUILDKIT_HOST=tcp://0.0.0.0:1234
|
|
||||||
|
|
||||||
env:
|
|
||||||
global:
|
|
||||||
- PLATFORMS="linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/s390x,linux/ppc64le"
|
|
||||||
- CROSS_PLATFORMS="${PLATFORMS},darwin/amd64,windows/amd64"
|
|
||||||
- PREFER_BUILDCTL="1"
|
|
||||||
|
|
||||||
script:
|
|
||||||
- make binaries validate-all && TARGETPLATFORM="${CROSS_PLATFORMS}" ./hack/cross
|
|
||||||
|
|
||||||
|
|
||||||
deploy:
|
|
||||||
- provider: script
|
|
||||||
script: PLATFORMS="${CROSS_PLATFORMS}" ./hack/release $TRAVIS_TAG release-out
|
|
||||||
on:
|
|
||||||
repo: docker/buildx
|
|
||||||
tags: true
|
|
||||||
condition: $TRAVIS_TAG =~ ^v[0-9]
|
|
||||||
- provider: releases
|
|
||||||
api_key:
|
|
||||||
secure: "VKVL+tyS3BfqjM4VMGHoHJbcKY4mqq4AGrclVEvBnt0gm1LkGeKxSheCZgF1EC4oSV8rCy6dkoRWL0PLkl895MIl20Z4v53o1NOQ4Fn0A+eptnrld8jYUkL5PcD+kdEqv2GkBn7vO6E/fwYY/wH9FYlE+fXUa0c/YQGqNGS+XVDtgkftqBV+F2EzaIwk+D+QClFBRmKvIbXrUQASi1K6K2eT3gvzR4zh679TSdI2nbnTKtE06xG1PBFVmb1Ux3/Jz4yHFvf2d3M1mOyqIBsozKoyxisiFQxnm3FjhPrdlZJ9oy/nsQM3ahQKJ3DF8hiLI1LxcxRa6wo//t3uu2eJSYl/c5nu0T7gVw4sChQNy52fUhEGoDTDwYoAxsLSDXcpj1jevRsKvxt/dh2e2De1a9HYj5oM+z2O+pcyiY98cKDbhe2miUqUdiYMBy24xUunB46zVcJF3pIqCYtw5ts8ES6Ixn3u+4OGV/hMDrVdiG2bOZtNVkdbKMEkOEBGa3parPJ69jh6og639kdAD3DFxyZn3YKYuJlcNShn3tj6iPokBYhlLwwf8vuEV7gK7G0rDS9yxuF03jgkwpBBF2wy+u1AbJv241T7v2ZB8H8VlYyHA0E5pnoWbw+lIOTy4IAc8gIesMvDuFFi4r1okhiAt/24U0p4aAohjh1nPuU3spY="
|
|
||||||
file: release-out/**/*
|
|
||||||
skip_cleanup: true
|
|
||||||
file_glob: true
|
|
||||||
on:
|
|
||||||
repo: docker/buildx
|
|
||||||
tags: true
|
|
||||||
condition: $TRAVIS_TAG =~ ^v[0-9]
|
|
||||||
40
AUTHORS
40
AUTHORS
@@ -1,7 +1,45 @@
|
|||||||
# This file lists all individuals having contributed content to the repository.
|
# This file lists all individuals having contributed content to the repository.
|
||||||
# For how it is generated, see `scripts/generate-authors.sh`.
|
# For how it is generated, see hack/dockerfiles/authors.Dockerfile.
|
||||||
|
|
||||||
|
Akihiro Suda <akihiro.suda.cz@hco.ntt.co.jp>
|
||||||
|
Alex Couture-Beil <alex@earthly.dev>
|
||||||
|
Andrew Haines <andrew.haines@zencargo.com>
|
||||||
|
Andy MacKinlay <admackin@users.noreply.github.com>
|
||||||
|
Anthony Poschen <zanven42@gmail.com>
|
||||||
|
Artur Klauser <Artur.Klauser@computer.org>
|
||||||
|
Batuhan Apaydın <developerguy2@gmail.com>
|
||||||
Bin Du <bindu@microsoft.com>
|
Bin Du <bindu@microsoft.com>
|
||||||
|
Brandon Philips <brandon@ifup.org>
|
||||||
Brian Goff <cpuguy83@gmail.com>
|
Brian Goff <cpuguy83@gmail.com>
|
||||||
|
CrazyMax <github@crazymax.dev>
|
||||||
|
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
|
||||||
|
Devin Bayer <dev@doubly.so>
|
||||||
|
Djordje Lukic <djordje.lukic@docker.com>
|
||||||
|
Dmytro Makovey <dmytro.makovey@docker.com>
|
||||||
|
Donghui Wang <977675308@qq.com>
|
||||||
|
faust <faustin@fala.red>
|
||||||
|
Felipe Santos <felipecassiors@gmail.com>
|
||||||
|
Fernando Miguel <github@FernandoMiguel.net>
|
||||||
|
gfrancesco <gfrancesco@users.noreply.github.com>
|
||||||
|
gracenoah <gracenoahgh@gmail.com>
|
||||||
|
Hollow Man <hollowman@hollowman.ml>
|
||||||
|
Ilya Dmitrichenko <errordeveloper@gmail.com>
|
||||||
|
Jack Laxson <jackjrabbit@gmail.com>
|
||||||
|
Jean-Yves Gastaud <jygastaud@gmail.com>
|
||||||
|
khs1994 <khs1994@khs1994.com>
|
||||||
|
Kotaro Adachi <k33asby@gmail.com>
|
||||||
|
l00397676 <lujingxiao@huawei.com>
|
||||||
|
Michal Augustyn <michal.augustyn@mail.com>
|
||||||
|
Patrick Van Stee <patrick@vanstee.me>
|
||||||
|
Saul Shanabrook <s.shanabrook@gmail.com>
|
||||||
|
Sebastiaan van Stijn <github@gone.nl>
|
||||||
|
SHIMA Tatsuya <ts1s1andn@gmail.com>
|
||||||
|
Silvin Lubecki <silvin.lubecki@docker.com>
|
||||||
|
Solomon Hykes <sh.github.6811@hykes.org>
|
||||||
|
Sune Keller <absukl@almbrand.dk>
|
||||||
Tibor Vass <tibor@docker.com>
|
Tibor Vass <tibor@docker.com>
|
||||||
Tõnis Tiigi <tonistiigi@gmail.com>
|
Tõnis Tiigi <tonistiigi@gmail.com>
|
||||||
|
Ulysses Souza <ulyssessouza@gmail.com>
|
||||||
|
Wang Jinglei <morlay.null@gmail.com>
|
||||||
|
Xiang Dai <764524258@qq.com>
|
||||||
|
zelahi <elahi.zuhayr@gmail.com>
|
||||||
|
|||||||
123
Dockerfile
123
Dockerfile
@@ -1,76 +1,127 @@
|
|||||||
# syntax=docker/dockerfile:1.1-experimental
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG DOCKERD_VERSION=19.03-rc
|
ARG GO_VERSION=1.21
|
||||||
ARG CLI_VERSION=19.03
|
ARG XX_VERSION=1.4.0
|
||||||
|
|
||||||
FROM docker:$DOCKERD_VERSION AS dockerd-release
|
# for testing
|
||||||
|
ARG DOCKER_VERSION=26.0.0
|
||||||
|
ARG GOTESTSUM_VERSION=v1.9.0
|
||||||
|
ARG REGISTRY_VERSION=2.8.0
|
||||||
|
ARG BUILDKIT_VERSION=v0.13.1
|
||||||
|
ARG UNDOCK_VERSION=0.7.0
|
||||||
|
|
||||||
# xgo is a helper for golang cross-compilation
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:golang@sha256:6f7d999551dd471b58f70716754290495690efa8421e0a1fcf18eb11d0c0a537 AS xgo
|
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 --platform=$BUILDPLATFORM golang:1.13-alpine AS gobase
|
FROM golatest AS gobase
|
||||||
COPY --from=xgo / /
|
COPY --from=xx / /
|
||||||
RUN apk add --no-cache file git
|
RUN apk add --no-cache file git
|
||||||
ENV GOFLAGS=-mod=vendor
|
ENV GOFLAGS=-mod=vendor
|
||||||
|
ENV CGO_ENABLED=0
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
|
FROM gobase AS gotestsum
|
||||||
|
ARG GOTESTSUM_VERSION
|
||||||
|
ENV GOFLAGS=
|
||||||
|
RUN --mount=target=/root/.cache,type=cache \
|
||||||
|
GOBIN=/out/ go install "gotest.tools/gotestsum@${GOTESTSUM_VERSION}" && \
|
||||||
|
/out/gotestsum --version
|
||||||
|
|
||||||
FROM gobase AS buildx-version
|
FROM gobase AS buildx-version
|
||||||
RUN --mount=target=. \
|
RUN --mount=type=bind,target=. <<EOT
|
||||||
PKG=github.com/docker/buildx VERSION=$(git describe --match 'v[0-9]*' --dirty='.m' --always --tags) REVISION=$(git rev-parse HEAD)$(if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi); \
|
set -e
|
||||||
echo "-X ${PKG}/version.Version=${VERSION} -X ${PKG}/version.Revision=${REVISION} -X ${PKG}/version.Package=${PKG}" | tee /tmp/.ldflags; \
|
mkdir /buildx-version
|
||||||
echo -n "${VERSION}" | tee /tmp/.version;
|
echo -n "$(./hack/git-meta version)" | tee /buildx-version/version
|
||||||
|
echo -n "$(./hack/git-meta revision)" | tee /buildx-version/revision
|
||||||
|
EOT
|
||||||
|
|
||||||
FROM gobase AS buildx-build
|
FROM gobase AS buildx-build
|
||||||
ENV CGO_ENABLED=0
|
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
RUN --mount=target=. --mount=target=/root/.cache,type=cache \
|
RUN --mount=type=bind,target=. \
|
||||||
--mount=target=/go/pkg/mod,type=cache \
|
--mount=type=cache,target=/root/.cache \
|
||||||
--mount=source=/tmp/.ldflags,target=/tmp/.ldflags,from=buildx-version \
|
--mount=type=cache,target=/go/pkg/mod \
|
||||||
set -x; go build -ldflags "$(cat /tmp/.ldflags)" -o /usr/bin/buildx ./cmd/buildx && \
|
--mount=type=bind,from=buildx-version,source=/buildx-version,target=/buildx-version <<EOT
|
||||||
file /usr/bin/buildx && file /usr/bin/buildx | egrep "statically linked|Mach-O|Windows"
|
set -e
|
||||||
|
xx-go --wrap
|
||||||
|
DESTDIR=/usr/bin VERSION=$(cat /buildx-version/version) REVISION=$(cat /buildx-version/revision) GO_EXTRA_LDFLAGS="-s -w" ./hack/build
|
||||||
|
xx-verify --static /usr/bin/docker-buildx
|
||||||
|
EOT
|
||||||
|
|
||||||
FROM buildx-build AS integration-tests
|
FROM gobase AS test
|
||||||
COPY . .
|
ENV SKIP_INTEGRATION_TESTS=1
|
||||||
|
RUN --mount=type=bind,target=. \
|
||||||
|
--mount=type=cache,target=/root/.cache \
|
||||||
|
--mount=type=cache,target=/go/pkg/mod \
|
||||||
|
go test -v -coverprofile=/tmp/coverage.txt -covermode=atomic ./... && \
|
||||||
|
go tool cover -func=/tmp/coverage.txt
|
||||||
|
|
||||||
# FROM golang:1.12-alpine AS docker-cli-build
|
FROM scratch AS test-coverage
|
||||||
# RUN apk add -U git bash coreutils gcc musl-dev
|
COPY --from=test /tmp/coverage.txt /coverage.txt
|
||||||
# ENV CGO_ENABLED=0
|
|
||||||
# ARG REPO=github.com/tiborvass/cli
|
|
||||||
# ARG BRANCH=cli-plugin-aliases
|
|
||||||
# ARG CLI_VERSION
|
|
||||||
# WORKDIR /go/src/github.com/docker/cli
|
|
||||||
# RUN git clone git://$REPO . && git checkout $BRANCH
|
|
||||||
# RUN ./scripts/build/binary
|
|
||||||
|
|
||||||
FROM scratch AS binaries-unix
|
FROM scratch AS binaries-unix
|
||||||
COPY --from=buildx-build /usr/bin/buildx /
|
COPY --link --from=buildx-build /usr/bin/docker-buildx /buildx
|
||||||
|
|
||||||
FROM binaries-unix AS binaries-darwin
|
FROM binaries-unix AS binaries-darwin
|
||||||
FROM binaries-unix AS binaries-linux
|
FROM binaries-unix AS binaries-linux
|
||||||
|
|
||||||
FROM scratch AS binaries-windows
|
FROM scratch AS binaries-windows
|
||||||
COPY --from=buildx-build /usr/bin/buildx /buildx.exe
|
COPY --link --from=buildx-build /usr/bin/docker-buildx /buildx.exe
|
||||||
|
|
||||||
FROM binaries-$TARGETOS AS binaries
|
FROM binaries-$TARGETOS AS binaries
|
||||||
|
# enable scanning for this stage
|
||||||
|
ARG BUILDKIT_SBOM_SCAN_STAGE=true
|
||||||
|
|
||||||
|
FROM gobase AS integration-test-base
|
||||||
|
# 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
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Release
|
||||||
FROM --platform=$BUILDPLATFORM alpine AS releaser
|
FROM --platform=$BUILDPLATFORM alpine AS releaser
|
||||||
WORKDIR /work
|
WORKDIR /work
|
||||||
ARG TARGETPLATFORM
|
ARG TARGETPLATFORM
|
||||||
RUN --mount=from=binaries \
|
RUN --mount=from=binaries \
|
||||||
--mount=source=/tmp/.version,target=/tmp/.version,from=buildx-version \
|
--mount=type=bind,from=buildx-version,source=/buildx-version,target=/buildx-version <<EOT
|
||||||
mkdir -p /out && cp buildx* "/out/buildx-$(cat /tmp/.version).$(echo $TARGETPLATFORM | sed 's/\//-/g')$(ls buildx* | sed -e 's/^buildx//')"
|
set -e
|
||||||
|
mkdir -p /out
|
||||||
|
cp buildx* "/out/buildx-$(cat /buildx-version/version).$(echo $TARGETPLATFORM | sed 's/\//-/g')$(ls buildx* | sed -e 's/^buildx//')"
|
||||||
|
EOT
|
||||||
|
|
||||||
FROM scratch AS release
|
FROM scratch AS release
|
||||||
COPY --from=releaser /out/ /
|
COPY --from=releaser /out/ /
|
||||||
|
|
||||||
FROM alpine AS demo-env
|
# Shell
|
||||||
|
FROM docker:$DOCKER_VERSION AS dockerd-release
|
||||||
|
FROM alpine AS shell
|
||||||
RUN apk add --no-cache iptables tmux git vim less openssh
|
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
|
RUN mkdir -p /usr/local/lib/docker/cli-plugins && ln -s /usr/local/bin/buildx /usr/local/lib/docker/cli-plugins/docker-buildx
|
||||||
COPY ./hack/demo-env/entrypoint.sh /usr/local/bin
|
COPY ./hack/demo-env/entrypoint.sh /usr/local/bin
|
||||||
COPY ./hack/demo-env/tmux.conf /root/.tmux.conf
|
COPY ./hack/demo-env/tmux.conf /root/.tmux.conf
|
||||||
COPY --from=dockerd-release /usr/local/bin /usr/local/bin
|
COPY --from=dockerd-release /usr/local/bin /usr/local/bin
|
||||||
#COPY --from=docker-cli-build /go/src/github.com/docker/cli/build/docker /usr/local/bin
|
|
||||||
|
|
||||||
WORKDIR /work
|
WORKDIR /work
|
||||||
COPY ./hack/demo-env/examples .
|
COPY ./hack/demo-env/examples .
|
||||||
COPY --from=binaries / /usr/local/bin/
|
COPY --from=binaries / /usr/local/bin/
|
||||||
|
|||||||
18
MAINTAINERS
18
MAINTAINERS
@@ -150,6 +150,9 @@ made through a pull request.
|
|||||||
[Org.Maintainers]
|
[Org.Maintainers]
|
||||||
|
|
||||||
people = [
|
people = [
|
||||||
|
"akihirosuda",
|
||||||
|
"crazy-max",
|
||||||
|
"jedevc",
|
||||||
"tiborvass",
|
"tiborvass",
|
||||||
"tonistiigi",
|
"tonistiigi",
|
||||||
]
|
]
|
||||||
@@ -176,6 +179,21 @@ made through a pull request.
|
|||||||
# All other sections should refer to people by their canonical key
|
# All other sections should refer to people by their canonical key
|
||||||
# in the people section.
|
# in the people section.
|
||||||
|
|
||||||
|
[people.akihirosuda]
|
||||||
|
Name = "Akihiro Suda"
|
||||||
|
Email = "akihiro.suda.cz@hco.ntt.co.jp"
|
||||||
|
GitHub = "AkihiroSuda"
|
||||||
|
|
||||||
|
[people.crazy-max]
|
||||||
|
Name = "Kevin Alvarez"
|
||||||
|
Email = "contact@crazymax.dev"
|
||||||
|
GitHub = "crazy-max"
|
||||||
|
|
||||||
|
[people.jedevc]
|
||||||
|
Name = "Justin Chadwell"
|
||||||
|
Email = "me@jedevc.com"
|
||||||
|
GitHub = "jedevc"
|
||||||
|
|
||||||
[people.thajeztah]
|
[people.thajeztah]
|
||||||
Name = "Sebastiaan van Stijn"
|
Name = "Sebastiaan van Stijn"
|
||||||
Email = "github@gone.nl"
|
Email = "github@gone.nl"
|
||||||
|
|||||||
83
Makefile
83
Makefile
@@ -1,31 +1,96 @@
|
|||||||
|
ifneq (, $(BUILDX_BIN))
|
||||||
|
export BUILDX_CMD = $(BUILDX_BIN)
|
||||||
|
else ifneq (, $(shell docker buildx version))
|
||||||
|
export BUILDX_CMD = docker buildx
|
||||||
|
else ifneq (, $(shell which buildx))
|
||||||
|
export BUILDX_CMD = $(which buildx)
|
||||||
|
endif
|
||||||
|
|
||||||
|
export BUILDX_CMD ?= docker buildx
|
||||||
|
|
||||||
|
.PHONY: all
|
||||||
|
all: binaries
|
||||||
|
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
./hack/build
|
||||||
|
|
||||||
|
.PHONY: shell
|
||||||
shell:
|
shell:
|
||||||
./hack/shell
|
./hack/shell
|
||||||
|
|
||||||
|
.PHONY: binaries
|
||||||
binaries:
|
binaries:
|
||||||
./hack/binaries
|
$(BUILDX_CMD) bake binaries
|
||||||
|
|
||||||
|
.PHONY: binaries-cross
|
||||||
binaries-cross:
|
binaries-cross:
|
||||||
EXPORT_LOCAL=cross-out ./hack/cross
|
$(BUILDX_CMD) bake binaries-cross
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
install: binaries
|
install: binaries
|
||||||
mkdir -p ~/.docker/cli-plugins
|
mkdir -p ~/.docker/cli-plugins
|
||||||
cp bin/buildx ~/.docker/cli-plugins/docker-buildx
|
install bin/build/buildx ~/.docker/cli-plugins/docker-buildx
|
||||||
|
|
||||||
|
.PHONY: release
|
||||||
|
release:
|
||||||
|
./hack/release
|
||||||
|
|
||||||
|
.PHONY: validate-all
|
||||||
|
validate-all: lint test validate-vendor validate-docs validate-generated-files
|
||||||
|
|
||||||
|
.PHONY: lint
|
||||||
lint:
|
lint:
|
||||||
./hack/lint
|
$(BUILDX_CMD) bake lint
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
test:
|
test:
|
||||||
./hack/test
|
./hack/test
|
||||||
|
|
||||||
|
.PHONY: test-unit
|
||||||
|
test-unit:
|
||||||
|
TESTPKGS=./... SKIP_INTEGRATION_TESTS=1 ./hack/test
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test-integration:
|
||||||
|
TESTPKGS=./tests ./hack/test
|
||||||
|
|
||||||
|
.PHONY: validate-vendor
|
||||||
validate-vendor:
|
validate-vendor:
|
||||||
./hack/validate-vendor
|
$(BUILDX_CMD) bake validate-vendor
|
||||||
|
|
||||||
validate-all: lint test validate-vendor
|
.PHONY: validate-docs
|
||||||
|
validate-docs:
|
||||||
|
$(BUILDX_CMD) bake validate-docs
|
||||||
|
|
||||||
|
.PHONY: validate-authors
|
||||||
|
validate-authors:
|
||||||
|
$(BUILDX_CMD) bake validate-authors
|
||||||
|
|
||||||
|
.PHONY: validate-generated-files
|
||||||
|
validate-generated-files:
|
||||||
|
$(BUILDX_CMD) bake validate-generated-files
|
||||||
|
|
||||||
|
.PHONY: test-driver
|
||||||
|
test-driver:
|
||||||
|
./hack/test-driver
|
||||||
|
|
||||||
|
.PHONY: vendor
|
||||||
vendor:
|
vendor:
|
||||||
./hack/update-vendor
|
./hack/update-vendor
|
||||||
|
|
||||||
generate-authors:
|
.PHONY: docs
|
||||||
./hack/generate-authors
|
docs:
|
||||||
|
./hack/update-docs
|
||||||
|
|
||||||
.PHONY: vendor lint shell binaries install binaries-cross validate-all generate-authors
|
.PHONY: authors
|
||||||
|
authors:
|
||||||
|
$(BUILDX_CMD) bake update-authors
|
||||||
|
|
||||||
|
.PHONY: mod-outdated
|
||||||
|
mod-outdated:
|
||||||
|
$(BUILDX_CMD) bake mod-outdated
|
||||||
|
|
||||||
|
.PHONY: generated-files
|
||||||
|
generated-files:
|
||||||
|
$(BUILDX_CMD) bake update-generated-files
|
||||||
|
|||||||
1417
bake/bake.go
1417
bake/bake.go
File diff suppressed because it is too large
Load Diff
1606
bake/bake_test.go
1606
bake/bake_test.go
File diff suppressed because it is too large
Load Diff
360
bake/compose.go
360
bake/compose.go
@@ -1,50 +1,53 @@
|
|||||||
package bake
|
package bake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/compose/loader"
|
"github.com/compose-spec/compose-go/v2/dotenv"
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseCompose(dt []byte) (*composetypes.Config, error) {
|
func ParseComposeFiles(fs []File) (*Config, error) {
|
||||||
parsed, err := loader.ParseYAML([]byte(dt))
|
envs, err := composeEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return loader.Load(composetypes.ConfigDetails{
|
var cfgs []composetypes.ConfigFile
|
||||||
ConfigFiles: []composetypes.ConfigFile{
|
for _, f := range fs {
|
||||||
{
|
cfgs = append(cfgs, composetypes.ConfigFile{
|
||||||
Config: parsed,
|
Filename: f.Name,
|
||||||
},
|
Content: f.Data,
|
||||||
},
|
})
|
||||||
Environment: envMap(os.Environ()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func envMap(env []string) map[string]string {
|
|
||||||
result := make(map[string]string, len(env))
|
|
||||||
for _, s := range env {
|
|
||||||
kv := strings.SplitN(s, "=", 2)
|
|
||||||
if len(kv) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
result[kv[0]] = kv[1]
|
|
||||||
}
|
}
|
||||||
return result
|
return ParseCompose(cfgs, envs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseCompose(dt []byte) (*Config, error) {
|
func ParseCompose(cfgs []composetypes.ConfigFile, envs map[string]string) (*Config, error) {
|
||||||
cfg, err := parseCompose(dt)
|
if envs == nil {
|
||||||
|
envs = make(map[string]string)
|
||||||
|
}
|
||||||
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var c Config
|
var c Config
|
||||||
var zeroBuildConfig composetypes.BuildConfig
|
|
||||||
if len(cfg.Services) > 0 {
|
if len(cfg.Services) > 0 {
|
||||||
c.Groups = []*Group{}
|
c.Groups = []*Group{}
|
||||||
c.Targets = []*Target{}
|
c.Targets = []*Target{}
|
||||||
@@ -52,15 +55,16 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||||||
g := &Group{Name: "default"}
|
g := &Group{Name: "default"}
|
||||||
|
|
||||||
for _, s := range cfg.Services {
|
for _, s := range cfg.Services {
|
||||||
|
s := s
|
||||||
if reflect.DeepEqual(s.Build, zeroBuildConfig) {
|
if s.Build == nil {
|
||||||
// if not make sure they're setting an image or it's invalid d-c.yml
|
|
||||||
if s.Image == "" {
|
|
||||||
return nil, fmt.Errorf("compose file invalid: service %s has neither an image nor a build context specified. At least one must be provided.", s.Name)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targetName := sanitizeTargetName(s.Name)
|
||||||
|
if err = validateTargetName(targetName); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "invalid service name %q", targetName)
|
||||||
|
}
|
||||||
|
|
||||||
var contextPathP *string
|
var contextPathP *string
|
||||||
if s.Build.Context != "" {
|
if s.Build.Context != "" {
|
||||||
contextPath := s.Build.Context
|
contextPath := s.Build.Context
|
||||||
@@ -71,21 +75,85 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||||||
dockerfilePath := s.Build.Dockerfile
|
dockerfilePath := s.Build.Dockerfile
|
||||||
dockerfilePathP = &dockerfilePath
|
dockerfilePathP = &dockerfilePath
|
||||||
}
|
}
|
||||||
g.Targets = append(g.Targets, s.Name)
|
var dockerfileInlineP *string
|
||||||
|
if s.Build.DockerfileInline != "" {
|
||||||
|
dockerfileInline := s.Build.DockerfileInline
|
||||||
|
dockerfileInlineP = &dockerfileInline
|
||||||
|
}
|
||||||
|
|
||||||
|
var additionalContexts map[string]string
|
||||||
|
if s.Build.AdditionalContexts != nil {
|
||||||
|
additionalContexts = map[string]string{}
|
||||||
|
for k, v := range s.Build.AdditionalContexts {
|
||||||
|
additionalContexts[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
secrets = append(secrets, secret)
|
||||||
|
}
|
||||||
|
|
||||||
|
// compose does not support nil values for labels
|
||||||
|
labels := map[string]*string{}
|
||||||
|
for k, v := range s.Build.Labels {
|
||||||
|
v := v
|
||||||
|
labels[k] = &v
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Targets = append(g.Targets, targetName)
|
||||||
t := &Target{
|
t := &Target{
|
||||||
Name: s.Name,
|
Name: targetName,
|
||||||
Context: contextPathP,
|
Context: contextPathP,
|
||||||
Dockerfile: dockerfilePathP,
|
Contexts: additionalContexts,
|
||||||
Labels: s.Build.Labels,
|
Dockerfile: dockerfilePathP,
|
||||||
Args: toMap(s.Build.Args),
|
DockerfileInline: dockerfileInlineP,
|
||||||
CacheFrom: s.Build.CacheFrom,
|
Tags: s.Build.Tags,
|
||||||
// TODO: add platforms
|
Labels: labels,
|
||||||
|
Args: flatten(s.Build.Args.Resolve(func(val string) (string, bool) {
|
||||||
|
if val, ok := s.Environment[val]; ok && val != nil {
|
||||||
|
return *val, true
|
||||||
|
}
|
||||||
|
val, ok := cfg.Environment[val]
|
||||||
|
return val, ok
|
||||||
|
})),
|
||||||
|
CacheFrom: s.Build.CacheFrom,
|
||||||
|
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
|
||||||
}
|
}
|
||||||
if s.Build.Target != "" {
|
if s.Build.Target != "" {
|
||||||
target := s.Build.Target
|
target := s.Build.Target
|
||||||
t.Target = &target
|
t.Target = &target
|
||||||
}
|
}
|
||||||
if s.Image != "" {
|
if len(t.Tags) == 0 && s.Image != "" {
|
||||||
t.Tags = []string{s.Image}
|
t.Tags = []string{s.Image}
|
||||||
}
|
}
|
||||||
c.Targets = append(c.Targets, t)
|
c.Targets = append(c.Targets, t)
|
||||||
@@ -97,14 +165,206 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||||||
return &c, nil
|
return &c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func toMap(in composetypes.MappingWithEquals) map[string]string {
|
func validateComposeFile(dt []byte, fn string) (bool, error) {
|
||||||
m := map[string]string{}
|
envs, err := composeEnv()
|
||||||
for k, v := range in {
|
if err != nil {
|
||||||
if v != nil {
|
return true, err
|
||||||
m[k] = *v
|
}
|
||||||
} else {
|
fnl := strings.ToLower(fn)
|
||||||
m[k] = os.Getenv(k)
|
if strings.HasSuffix(fnl, ".yml") || strings.HasSuffix(fnl, ".yaml") {
|
||||||
|
return true, validateCompose(dt, envs)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(fnl, ".json") || strings.HasSuffix(fnl, ".hcl") {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
err = validateCompose(dt, envs)
|
||||||
|
return err == nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateCompose(dt []byte, envs map[string]string) error {
|
||||||
|
_, err := loader.Load(composetypes.ConfigDetails{
|
||||||
|
ConfigFiles: []composetypes.ConfigFile{
|
||||||
|
{
|
||||||
|
Content: dt,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Environment: envs,
|
||||||
|
}, func(options *loader.Options) {
|
||||||
|
options.SetProjectName("bake", false)
|
||||||
|
options.SkipNormalization = true
|
||||||
|
// consistency is checked later in ParseCompose to ensure multiple
|
||||||
|
// compose files can be merged together
|
||||||
|
options.SkipConsistencyCheck = true
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func composeEnv() (map[string]string, error) {
|
||||||
|
envs := sliceToMap(os.Environ())
|
||||||
|
if wd, err := os.Getwd(); err == nil {
|
||||||
|
envs, err = loadDotEnv(envs, wd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m
|
return envs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDotEnv(curenv map[string]string, workingDir string) (map[string]string, error) {
|
||||||
|
if curenv == nil {
|
||||||
|
curenv = make(map[string]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
ef, err := filepath.Abs(filepath.Join(workingDir, ".env"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err = os.Stat(ef); os.IsNotExist(err) {
|
||||||
|
return curenv, nil
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := os.ReadFile(ef)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
envs, err := dotenv.UnmarshalBytesWithLookup(dt, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range envs {
|
||||||
|
if _, set := curenv[k]; set {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
curenv[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return curenv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func flatten(in composetypes.MappingWithEquals) map[string]*string {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := map[string]*string{}
|
||||||
|
for k, v := range in {
|
||||||
|
if v == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[k] = v
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// xbake Compose build extension provides fields not (yet) available in
|
||||||
|
// Compose build specification: https://github.com/compose-spec/compose-spec/blob/master/build.md
|
||||||
|
type xbake struct {
|
||||||
|
Tags stringArray `yaml:"tags,omitempty"`
|
||||||
|
CacheFrom stringArray `yaml:"cache-from,omitempty"`
|
||||||
|
CacheTo stringArray `yaml:"cache-to,omitempty"`
|
||||||
|
Secrets stringArray `yaml:"secret,omitempty"`
|
||||||
|
SSH stringArray `yaml:"ssh,omitempty"`
|
||||||
|
Platforms stringArray `yaml:"platforms,omitempty"`
|
||||||
|
Outputs stringArray `yaml:"output,omitempty"`
|
||||||
|
Pull *bool `yaml:"pull,omitempty"`
|
||||||
|
NoCache *bool `yaml:"no-cache,omitempty"`
|
||||||
|
NoCacheFilter stringArray `yaml:"no-cache-filter,omitempty"`
|
||||||
|
Contexts stringMap `yaml:"contexts,omitempty"`
|
||||||
|
// don't forget to update documentation if you add a new field:
|
||||||
|
// docs/manuals/bake/compose-file.md#extension-field-with-x-bake
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringMap map[string]string
|
||||||
|
type stringArray []string
|
||||||
|
|
||||||
|
func (sa *stringArray) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
var multi []string
|
||||||
|
err := unmarshal(&multi)
|
||||||
|
if err != nil {
|
||||||
|
var single string
|
||||||
|
if err := unmarshal(&single); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*sa = strings.Fields(single)
|
||||||
|
} else {
|
||||||
|
*sa = multi
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// composeExtTarget converts Compose build extension x-bake to bake Target
|
||||||
|
// https://github.com/compose-spec/compose-spec/blob/master/spec.md#extension
|
||||||
|
func (t *Target) composeExtTarget(exts map[string]interface{}) error {
|
||||||
|
var xb xbake
|
||||||
|
|
||||||
|
ext, ok := exts["x-bake"]
|
||||||
|
if !ok || ext == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
yb, _ := yaml.Marshal(ext)
|
||||||
|
if err := yaml.Unmarshal(yb, &xb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(xb.Tags) > 0 {
|
||||||
|
t.Tags = dedupSlice(append(t.Tags, xb.Tags...))
|
||||||
|
}
|
||||||
|
if len(xb.CacheFrom) > 0 {
|
||||||
|
t.CacheFrom = dedupSlice(append(t.CacheFrom, xb.CacheFrom...))
|
||||||
|
}
|
||||||
|
if len(xb.CacheTo) > 0 {
|
||||||
|
t.CacheTo = dedupSlice(append(t.CacheTo, xb.CacheTo...))
|
||||||
|
}
|
||||||
|
if len(xb.Secrets) > 0 {
|
||||||
|
t.Secrets = dedupSlice(append(t.Secrets, xb.Secrets...))
|
||||||
|
}
|
||||||
|
if len(xb.SSH) > 0 {
|
||||||
|
t.SSH = dedupSlice(append(t.SSH, xb.SSH...))
|
||||||
|
}
|
||||||
|
if len(xb.Platforms) > 0 {
|
||||||
|
t.Platforms = dedupSlice(append(t.Platforms, xb.Platforms...))
|
||||||
|
}
|
||||||
|
if len(xb.Outputs) > 0 {
|
||||||
|
t.Outputs = dedupSlice(append(t.Outputs, xb.Outputs...))
|
||||||
|
}
|
||||||
|
if xb.Pull != nil {
|
||||||
|
t.Pull = xb.Pull
|
||||||
|
}
|
||||||
|
if xb.NoCache != nil {
|
||||||
|
t.NoCache = xb.NoCache
|
||||||
|
}
|
||||||
|
if len(xb.NoCacheFilter) > 0 {
|
||||||
|
t.NoCacheFilter = dedupSlice(append(t.NoCacheFilter, xb.NoCacheFilter...))
|
||||||
|
}
|
||||||
|
if len(xb.Contexts) > 0 {
|
||||||
|
t.Contexts = dedupMap(t.Contexts, xb.Contexts)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// composeToBuildkitSecret converts secret from compose format to buildkit's
|
||||||
|
// csv format.
|
||||||
|
func composeToBuildkitSecret(inp composetypes.ServiceSecretConfig, psecret composetypes.SecretConfig) (string, error) {
|
||||||
|
if psecret.External {
|
||||||
|
return "", errors.Errorf("unsupported external secret %s", psecret.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var bkattrs []string
|
||||||
|
if inp.Source != "" {
|
||||||
|
bkattrs = append(bkattrs, "id="+inp.Source)
|
||||||
|
}
|
||||||
|
if psecret.File != "" {
|
||||||
|
bkattrs = append(bkattrs, "src="+psecret.File)
|
||||||
|
}
|
||||||
|
if psecret.Environment != "" {
|
||||||
|
bkattrs = append(bkattrs, "env="+psecret.Environment)
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(bkattrs, ","), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package bake
|
package bake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
composetypes "github.com/compose-spec/compose-go/v2/types"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseCompose(t *testing.T) {
|
func TestParseCompose(t *testing.T) {
|
||||||
var dt = []byte(`
|
var dt = []byte(`
|
||||||
version: "3"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
build: ./db
|
build: ./db
|
||||||
@@ -19,52 +21,85 @@ services:
|
|||||||
webapp:
|
webapp:
|
||||||
build:
|
build:
|
||||||
context: ./dir
|
context: ./dir
|
||||||
|
additional_contexts:
|
||||||
|
foo: ./bar
|
||||||
dockerfile: Dockerfile-alternate
|
dockerfile: Dockerfile-alternate
|
||||||
|
network:
|
||||||
|
none
|
||||||
args:
|
args:
|
||||||
buildno: 123
|
buildno: 123
|
||||||
|
cache_from:
|
||||||
|
- type=local,src=path/to/cache
|
||||||
|
cache_to:
|
||||||
|
- type=local,dest=path/to/cache
|
||||||
|
secrets:
|
||||||
|
- token
|
||||||
|
- aws
|
||||||
|
webapp2:
|
||||||
|
profiles:
|
||||||
|
- test
|
||||||
|
build:
|
||||||
|
context: ./dir
|
||||||
|
dockerfile_inline: |
|
||||||
|
FROM alpine
|
||||||
|
secrets:
|
||||||
|
token:
|
||||||
|
environment: ENV_TOKEN
|
||||||
|
aws:
|
||||||
|
file: /root/.aws/credentials
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Groups))
|
require.Equal(t, 1, len(c.Groups))
|
||||||
require.Equal(t, c.Groups[0].Name, "default")
|
require.Equal(t, "default", c.Groups[0].Name)
|
||||||
sort.Strings(c.Groups[0].Targets)
|
sort.Strings(c.Groups[0].Targets)
|
||||||
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
require.Equal(t, []string{"db", "webapp", "webapp2"}, c.Groups[0].Targets)
|
||||||
|
|
||||||
require.Equal(t, 2, len(c.Targets))
|
require.Equal(t, 3, len(c.Targets))
|
||||||
sort.Slice(c.Targets, func(i, j int) bool {
|
sort.Slice(c.Targets, func(i, j int) bool {
|
||||||
return c.Targets[i].Name < c.Targets[j].Name
|
return c.Targets[i].Name < c.Targets[j].Name
|
||||||
})
|
})
|
||||||
require.Equal(t, "db", c.Targets[0].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, "webapp", c.Targets[1].Name)
|
||||||
require.Equal(t, "./dir", *c.Targets[1].Context)
|
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, "Dockerfile-alternate", *c.Targets[1].Dockerfile)
|
||||||
require.Equal(t, 1, len(c.Targets[1].Args))
|
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||||
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
require.Equal(t, ptrstr("123"), c.Targets[1].Args["buildno"])
|
||||||
|
require.Equal(t, []string{"type=local,src=path/to/cache"}, c.Targets[1].CacheFrom)
|
||||||
|
require.Equal(t, []string{"type=local,dest=path/to/cache"}, c.Targets[1].CacheTo)
|
||||||
|
require.Equal(t, "none", *c.Targets[1].NetworkMode)
|
||||||
|
require.Equal(t, []string{
|
||||||
|
"id=token,env=ENV_TOKEN",
|
||||||
|
"id=aws,src=/root/.aws/credentials",
|
||||||
|
}, c.Targets[1].Secrets)
|
||||||
|
|
||||||
|
require.Equal(t, "webapp2", c.Targets[2].Name)
|
||||||
|
require.Equal(t, "dir", *c.Targets[2].Context)
|
||||||
|
require.Equal(t, "FROM alpine\n", *c.Targets[2].DockerfileInline)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoBuildOutOfTreeService(t *testing.T) {
|
func TestNoBuildOutOfTreeService(t *testing.T) {
|
||||||
var dt = []byte(`
|
var dt = []byte(`
|
||||||
version: "3.7"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
external:
|
external:
|
||||||
image: "verycooldb:1337"
|
image: "verycooldb:1337"
|
||||||
webapp:
|
webapp:
|
||||||
build: ./db
|
build: ./db
|
||||||
`)
|
`)
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 1, len(c.Groups))
|
require.Equal(t, 1, len(c.Groups))
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestParseComposeTarget(t *testing.T) {
|
func TestParseComposeTarget(t *testing.T) {
|
||||||
var dt = []byte(`
|
var dt = []byte(`
|
||||||
version: "3.7"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
build:
|
build:
|
||||||
@@ -76,7 +111,7 @@ services:
|
|||||||
target: webapp
|
target: webapp
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, 2, len(c.Targets))
|
require.Equal(t, 2, len(c.Targets))
|
||||||
@@ -91,8 +126,6 @@ services:
|
|||||||
|
|
||||||
func TestComposeBuildWithoutContext(t *testing.T) {
|
func TestComposeBuildWithoutContext(t *testing.T) {
|
||||||
var dt = []byte(`
|
var dt = []byte(`
|
||||||
version: "3.7"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
build:
|
build:
|
||||||
@@ -103,33 +136,626 @@ services:
|
|||||||
target: webapp
|
target: webapp
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, 2, len(c.Targets))
|
require.Equal(t, 2, len(c.Targets))
|
||||||
sort.Slice(c.Targets, func(i, j int) bool {
|
sort.Slice(c.Targets, func(i, j int) bool {
|
||||||
return c.Targets[i].Name < c.Targets[j].Name
|
return c.Targets[i].Name < c.Targets[j].Name
|
||||||
})
|
})
|
||||||
require.Equal(t, c.Targets[0].Name, "db")
|
require.Equal(t, "db", c.Targets[0].Name)
|
||||||
require.Equal(t, "db", *c.Targets[0].Target)
|
require.Equal(t, "db", *c.Targets[0].Target)
|
||||||
require.Equal(t, c.Targets[1].Name, "webapp")
|
require.Equal(t, "webapp", c.Targets[1].Name)
|
||||||
require.Equal(t, "webapp", *c.Targets[1].Target)
|
require.Equal(t, "webapp", *c.Targets[1].Target)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBogusCompose(t *testing.T) {
|
func TestBuildArgEnvCompose(t *testing.T) {
|
||||||
var dt = []byte(`
|
var dt = []byte(`
|
||||||
version: "3.7"
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
example:
|
||||||
labels:
|
image: example
|
||||||
- "foo"
|
|
||||||
webapp:
|
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
target: webapp
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
FOO:
|
||||||
|
BAR: $ZZZ_BAR
|
||||||
|
BRB: FOO
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := ParseCompose(dt)
|
t.Setenv("FOO", "bar")
|
||||||
require.Error(t, err)
|
t.Setenv("BAR", "foo")
|
||||||
require.Contains(t, err.Error(), "has neither an image nor a build context specified. At least one must be provided")
|
t.Setenv("ZZZ_BAR", "zzz_foo")
|
||||||
|
|
||||||
|
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"])
|
||||||
|
require.Equal(t, ptrstr("FOO"), c.Targets[0].Args["BRB"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInconsistentComposeFile(t *testing.T) {
|
||||||
|
var dt = []byte(`
|
||||||
|
services:
|
||||||
|
webapp:
|
||||||
|
entrypoint: echo 1
|
||||||
|
`)
|
||||||
|
|
||||||
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdvancedNetwork(t *testing.T) {
|
||||||
|
var dt = []byte(`
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
networks:
|
||||||
|
- example.com
|
||||||
|
build:
|
||||||
|
context: ./db
|
||||||
|
target: db
|
||||||
|
|
||||||
|
networks:
|
||||||
|
example.com:
|
||||||
|
name: example.com
|
||||||
|
driver: bridge
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 10.5.0.0/24
|
||||||
|
ip_range: 10.5.0.0/24
|
||||||
|
gateway: 10.5.0.254
|
||||||
|
`)
|
||||||
|
|
||||||
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTags(t *testing.T) {
|
||||||
|
var dt = []byte(`
|
||||||
|
services:
|
||||||
|
example:
|
||||||
|
image: example
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
tags:
|
||||||
|
- foo
|
||||||
|
- bar
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []string{"foo", "bar"}, c.Targets[0].Tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDependsOnList(t *testing.T) {
|
||||||
|
var dt = []byte(`
|
||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
example-container:
|
||||||
|
image: example/fails:latest
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
depends_on:
|
||||||
|
other-container:
|
||||||
|
condition: service_healthy
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
aliases:
|
||||||
|
- integration-tests
|
||||||
|
|
||||||
|
other-container:
|
||||||
|
image: example/other:latest
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "echo", "success"]
|
||||||
|
retries: 5
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
start_period: 5s
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: test-net
|
||||||
|
`)
|
||||||
|
|
||||||
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposeExt(t *testing.T) {
|
||||||
|
var dt = []byte(`
|
||||||
|
services:
|
||||||
|
addon:
|
||||||
|
image: ct-addon:bar
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
cache_from:
|
||||||
|
- user/app:cache
|
||||||
|
cache_to:
|
||||||
|
- user/app:cache
|
||||||
|
tags:
|
||||||
|
- ct-addon:baz
|
||||||
|
args:
|
||||||
|
CT_ECR: foo
|
||||||
|
CT_TAG: bar
|
||||||
|
x-bake:
|
||||||
|
contexts:
|
||||||
|
alpine: docker-image://alpine:3.13
|
||||||
|
tags:
|
||||||
|
- ct-addon:foo
|
||||||
|
- ct-addon:alp
|
||||||
|
platforms:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
cache-from:
|
||||||
|
- type=local,src=path/to/cache
|
||||||
|
cache-to:
|
||||||
|
- type=local,dest=path/to/cache
|
||||||
|
pull: true
|
||||||
|
|
||||||
|
aws:
|
||||||
|
image: ct-fake-aws:bar
|
||||||
|
build:
|
||||||
|
dockerfile: ./aws.Dockerfile
|
||||||
|
args:
|
||||||
|
CT_ECR: foo
|
||||||
|
CT_TAG: bar
|
||||||
|
shm_size: 128m
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 1024
|
||||||
|
hard: 1024
|
||||||
|
x-bake:
|
||||||
|
secret:
|
||||||
|
- id=mysecret,src=/local/secret
|
||||||
|
- id=mysecret2,src=/local/secret2
|
||||||
|
ssh: default
|
||||||
|
platforms: linux/arm64
|
||||||
|
output: type=docker
|
||||||
|
no-cache: true
|
||||||
|
`)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return c.Targets[i].Name < c.Targets[j].Name
|
||||||
|
})
|
||||||
|
require.Equal(t, map[string]*string{"CT_ECR": ptrstr("foo"), "CT_TAG": ptrstr("bar")}, c.Targets[0].Args)
|
||||||
|
require.Equal(t, []string{"ct-addon:baz", "ct-addon:foo", "ct-addon:alp"}, c.Targets[0].Tags)
|
||||||
|
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[0].Platforms)
|
||||||
|
require.Equal(t, []string{"user/app:cache", "type=local,src=path/to/cache"}, c.Targets[0].CacheFrom)
|
||||||
|
require.Equal(t, []string{"user/app:cache", "type=local,dest=path/to/cache"}, c.Targets[0].CacheTo)
|
||||||
|
require.Equal(t, newBool(true), c.Targets[0].Pull)
|
||||||
|
require.Equal(t, map[string]string{"alpine": "docker-image://alpine:3.13"}, c.Targets[0].Contexts)
|
||||||
|
require.Equal(t, []string{"ct-fake-aws:bar"}, c.Targets[1].Tags)
|
||||||
|
require.Equal(t, []string{"id=mysecret,src=/local/secret", "id=mysecret2,src=/local/secret2"}, c.Targets[1].Secrets)
|
||||||
|
require.Equal(t, []string{"default"}, c.Targets[1].SSH)
|
||||||
|
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) {
|
||||||
|
var dt = []byte(`
|
||||||
|
services:
|
||||||
|
webapp:
|
||||||
|
image: app:bar
|
||||||
|
build:
|
||||||
|
cache_from:
|
||||||
|
- user/app:cache
|
||||||
|
cache_to:
|
||||||
|
- user/app:cache
|
||||||
|
tags:
|
||||||
|
- ct-addon:foo
|
||||||
|
x-bake:
|
||||||
|
tags:
|
||||||
|
- ct-addon:foo
|
||||||
|
- ct-addon:baz
|
||||||
|
cache-from:
|
||||||
|
- user/app:cache
|
||||||
|
- type=local,src=path/to/cache
|
||||||
|
cache-to:
|
||||||
|
- type=local,dest=path/to/cache
|
||||||
|
`)
|
||||||
|
|
||||||
|
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)
|
||||||
|
require.Equal(t, []string{"user/app:cache", "type=local,src=path/to/cache"}, c.Targets[0].CacheFrom)
|
||||||
|
require.Equal(t, []string{"user/app:cache", "type=local,dest=path/to/cache"}, c.Targets[0].CacheTo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnv(t *testing.T) {
|
||||||
|
envf, err := os.CreateTemp("", "env")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.Remove(envf.Name())
|
||||||
|
|
||||||
|
_, err = envf.WriteString("FOO=bsdf -csdf\n")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var dt = []byte(`
|
||||||
|
services:
|
||||||
|
scratch:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
CT_ECR: foo
|
||||||
|
FOO:
|
||||||
|
NODE_ENV:
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=test
|
||||||
|
- AWS_ACCESS_KEY_ID=dummy
|
||||||
|
- AWS_SECRET_ACCESS_KEY=dummy
|
||||||
|
env_file:
|
||||||
|
- ` + envf.Name() + `
|
||||||
|
`)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDotEnv(t *testing.T) {
|
||||||
|
tmpdir := t.TempDir()
|
||||||
|
|
||||||
|
err := os.WriteFile(filepath.Join(tmpdir, ".env"), []byte("FOO=bar"), 0644)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var dt = []byte(`
|
||||||
|
services:
|
||||||
|
scratch:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
FOO:
|
||||||
|
`)
|
||||||
|
|
||||||
|
chdir(t, tmpdir)
|
||||||
|
c, err := ParseComposeFiles([]File{{
|
||||||
|
Name: "docker-compose.yml",
|
||||||
|
Data: dt,
|
||||||
|
}})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, map[string]*string{"FOO": ptrstr("bar")}, c.Targets[0].Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPorts(t *testing.T) {
|
||||||
|
var dt = []byte(`
|
||||||
|
services:
|
||||||
|
foo:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
ports:
|
||||||
|
- 3306:3306
|
||||||
|
bar:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
ports:
|
||||||
|
- mode: ingress
|
||||||
|
target: 3306
|
||||||
|
published: "3306"
|
||||||
|
protocol: tcp
|
||||||
|
`)
|
||||||
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: dt}}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBool(val bool) *bool {
|
||||||
|
b := val
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServiceName(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
svc string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
svc: "a",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
svc: "abc",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
svc: "a.b",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
svc: "_a",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
svc: "a_b",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
svc: "AbC",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
svc: "AbC-0123",
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range cases {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.svc, func(t *testing.T) {
|
||||||
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: []byte(`
|
||||||
|
services:
|
||||||
|
` + tt.svc + `:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
`)}}, nil)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateComposeSecret(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
dt []byte
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "secret set by file",
|
||||||
|
dt: []byte(`
|
||||||
|
secrets:
|
||||||
|
foo:
|
||||||
|
file: .secret
|
||||||
|
`),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "secret set by environment",
|
||||||
|
dt: []byte(`
|
||||||
|
secrets:
|
||||||
|
foo:
|
||||||
|
environment: TOKEN
|
||||||
|
`),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "external secret",
|
||||||
|
dt: []byte(`
|
||||||
|
secrets:
|
||||||
|
foo:
|
||||||
|
external: true
|
||||||
|
`),
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unset secret",
|
||||||
|
dt: []byte(`
|
||||||
|
secrets:
|
||||||
|
foo: {}
|
||||||
|
`),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "undefined secret",
|
||||||
|
dt: []byte(`
|
||||||
|
services:
|
||||||
|
foo:
|
||||||
|
build:
|
||||||
|
secrets:
|
||||||
|
- token
|
||||||
|
`),
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range cases {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
_, err := ParseCompose([]composetypes.ConfigFile{{Content: tt.dt}}, nil)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateComposeFile(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
fn string
|
||||||
|
dt []byte
|
||||||
|
isCompose bool
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty service",
|
||||||
|
fn: "docker-compose.yml",
|
||||||
|
dt: []byte(`
|
||||||
|
services:
|
||||||
|
foo:
|
||||||
|
`),
|
||||||
|
isCompose: true,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "build",
|
||||||
|
fn: "docker-compose.yml",
|
||||||
|
dt: []byte(`
|
||||||
|
services:
|
||||||
|
foo:
|
||||||
|
build: .
|
||||||
|
`),
|
||||||
|
isCompose: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "image",
|
||||||
|
fn: "docker-compose.yml",
|
||||||
|
dt: []byte(`
|
||||||
|
services:
|
||||||
|
simple:
|
||||||
|
image: nginx
|
||||||
|
`),
|
||||||
|
isCompose: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown ext",
|
||||||
|
fn: "docker-compose.foo",
|
||||||
|
dt: []byte(`
|
||||||
|
services:
|
||||||
|
simple:
|
||||||
|
image: nginx
|
||||||
|
`),
|
||||||
|
isCompose: true,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "hcl",
|
||||||
|
fn: "docker-bake.hcl",
|
||||||
|
dt: []byte(`
|
||||||
|
target "default" {
|
||||||
|
dockerfile = "test"
|
||||||
|
}
|
||||||
|
`),
|
||||||
|
isCompose: false,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range cases {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
isCompose, err := validateComposeFile(tt.dt, tt.fn)
|
||||||
|
assert.Equal(t, tt.isCompose, isCompose)
|
||||||
|
if tt.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestComposeNullArgs(t *testing.T) {
|
||||||
|
var dt = []byte(`
|
||||||
|
services:
|
||||||
|
scratch:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
args:
|
||||||
|
FOO: null
|
||||||
|
bar: "baz"
|
||||||
|
`)
|
||||||
|
|
||||||
|
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) {
|
||||||
|
olddir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("chdir: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.Chdir(dir); err != nil {
|
||||||
|
t.Fatalf("chdir %s: %v", dir, err)
|
||||||
|
}
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if err := os.Chdir(olddir); err != nil {
|
||||||
|
t.Errorf("chdir to original working directory %s: %v", olddir, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
216
bake/hcl.go
216
bake/hcl.go
@@ -1,174 +1,78 @@
|
|||||||
package bake
|
package bake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
hcl "github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/ext/userfunc"
|
"github.com/hashicorp/hcl/v2/hclparse"
|
||||||
"github.com/hashicorp/hcl/v2/hclsimple"
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
"github.com/moby/buildkit/solver/pb"
|
||||||
"github.com/hashicorp/hcl/v2/json"
|
|
||||||
"github.com/zclconf/go-cty/cty"
|
|
||||||
"github.com/zclconf/go-cty/cty/function"
|
|
||||||
"github.com/zclconf/go-cty/cty/function/stdlib"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Collection of generally useful functions in cty-using applications, which
|
func ParseHCLFile(dt []byte, fn string) (*hcl.File, bool, error) {
|
||||||
// HCL supports. These functions are available for use in HCL files.
|
var err error
|
||||||
var (
|
if strings.HasSuffix(fn, ".json") {
|
||||||
stdlibFunctions = map[string]function.Function{
|
f, diags := hclparse.NewParser().ParseJSON(dt, fn)
|
||||||
"absolute": stdlib.AbsoluteFunc,
|
if diags.HasErrors() {
|
||||||
"add": stdlib.AddFunc,
|
err = diags
|
||||||
"and": stdlib.AndFunc,
|
|
||||||
"byteslen": stdlib.BytesLenFunc,
|
|
||||||
"bytesslice": stdlib.BytesSliceFunc,
|
|
||||||
"chomp": stdlib.ChompFunc,
|
|
||||||
"chunklist": stdlib.ChunklistFunc,
|
|
||||||
"ceil": stdlib.CeilFunc,
|
|
||||||
"csvdecode": stdlib.CSVDecodeFunc,
|
|
||||||
"coalesce": stdlib.CoalesceFunc,
|
|
||||||
"coalescelist": stdlib.CoalesceListFunc,
|
|
||||||
"concat": stdlib.ConcatFunc,
|
|
||||||
"contains": stdlib.ContainsFunc,
|
|
||||||
"distinct": stdlib.DistinctFunc,
|
|
||||||
"divide": stdlib.DivideFunc,
|
|
||||||
"element": stdlib.ElementFunc,
|
|
||||||
"equal": stdlib.EqualFunc,
|
|
||||||
"flatten": stdlib.FlattenFunc,
|
|
||||||
"floor": stdlib.FloorFunc,
|
|
||||||
"formatdate": stdlib.FormatDateFunc,
|
|
||||||
"format": stdlib.FormatFunc,
|
|
||||||
"formatlist": stdlib.FormatListFunc,
|
|
||||||
"greaterthan": stdlib.GreaterThanFunc,
|
|
||||||
"greaterthanorequalto": stdlib.GreaterThanOrEqualToFunc,
|
|
||||||
"hasindex": stdlib.HasIndexFunc,
|
|
||||||
"indent": stdlib.IndentFunc,
|
|
||||||
"index": stdlib.IndexFunc,
|
|
||||||
"int": stdlib.IntFunc,
|
|
||||||
"jsondecode": stdlib.JSONDecodeFunc,
|
|
||||||
"jsonencode": stdlib.JSONEncodeFunc,
|
|
||||||
"keys": stdlib.KeysFunc,
|
|
||||||
"join": stdlib.JoinFunc,
|
|
||||||
"length": stdlib.LengthFunc,
|
|
||||||
"lessthan": stdlib.LessThanFunc,
|
|
||||||
"lessthanorequalto": stdlib.LessThanOrEqualToFunc,
|
|
||||||
"log": stdlib.LogFunc,
|
|
||||||
"lookup": stdlib.LookupFunc,
|
|
||||||
"lower": stdlib.LowerFunc,
|
|
||||||
"max": stdlib.MaxFunc,
|
|
||||||
"merge": stdlib.MergeFunc,
|
|
||||||
"min": stdlib.MinFunc,
|
|
||||||
"modulo": stdlib.ModuloFunc,
|
|
||||||
"multiply": stdlib.MultiplyFunc,
|
|
||||||
"negate": stdlib.NegateFunc,
|
|
||||||
"notequal": stdlib.NotEqualFunc,
|
|
||||||
"not": stdlib.NotFunc,
|
|
||||||
"or": stdlib.OrFunc,
|
|
||||||
"parseint": stdlib.ParseIntFunc,
|
|
||||||
"pow": stdlib.PowFunc,
|
|
||||||
"range": stdlib.RangeFunc,
|
|
||||||
"regexall": stdlib.RegexAllFunc,
|
|
||||||
"regex": stdlib.RegexFunc,
|
|
||||||
"reverse": stdlib.ReverseFunc,
|
|
||||||
"reverselist": stdlib.ReverseListFunc,
|
|
||||||
"sethaselement": stdlib.SetHasElementFunc,
|
|
||||||
"setintersection": stdlib.SetIntersectionFunc,
|
|
||||||
"setsubtract": stdlib.SetSubtractFunc,
|
|
||||||
"setsymmetricdifference": stdlib.SetSymmetricDifferenceFunc,
|
|
||||||
"setunion": stdlib.SetUnionFunc,
|
|
||||||
"signum": stdlib.SignumFunc,
|
|
||||||
"slice": stdlib.SliceFunc,
|
|
||||||
"sort": stdlib.SortFunc,
|
|
||||||
"split": stdlib.SplitFunc,
|
|
||||||
"strlen": stdlib.StrlenFunc,
|
|
||||||
"substr": stdlib.SubstrFunc,
|
|
||||||
"subtract": stdlib.SubtractFunc,
|
|
||||||
"timeadd": stdlib.TimeAddFunc,
|
|
||||||
"title": stdlib.TitleFunc,
|
|
||||||
"trim": stdlib.TrimFunc,
|
|
||||||
"trimprefix": stdlib.TrimPrefixFunc,
|
|
||||||
"trimspace": stdlib.TrimSpaceFunc,
|
|
||||||
"trimsuffix": stdlib.TrimSuffixFunc,
|
|
||||||
"upper": stdlib.UpperFunc,
|
|
||||||
"values": stdlib.ValuesFunc,
|
|
||||||
"zipmap": stdlib.ZipmapFunc,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Used in the first pass of decoding instead of the Config struct to disallow
|
|
||||||
// interpolation while parsing variable blocks.
|
|
||||||
type staticConfig struct {
|
|
||||||
Variables []*Variable `hcl:"variable,block"`
|
|
||||||
Remain hcl.Body `hcl:",remain"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseHCL(dt []byte, fn string) (*Config, error) {
|
|
||||||
// Decode user defined functions, first parsing as hcl and falling back to
|
|
||||||
// json, returning errors based on the file suffix.
|
|
||||||
file, hcldiags := hclsyntax.ParseConfig(dt, fn, hcl.Pos{Line: 1, Column: 1})
|
|
||||||
if hcldiags.HasErrors() {
|
|
||||||
var jsondiags hcl.Diagnostics
|
|
||||||
file, jsondiags = json.Parse(dt, fn)
|
|
||||||
if jsondiags.HasErrors() {
|
|
||||||
fnl := strings.ToLower(fn)
|
|
||||||
if strings.HasSuffix(fnl, ".json") {
|
|
||||||
return nil, jsondiags
|
|
||||||
} else {
|
|
||||||
return nil, hcldiags
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return f, true, err
|
||||||
}
|
}
|
||||||
|
if strings.HasSuffix(fn, ".hcl") {
|
||||||
userFunctions, _, diags := userfunc.DecodeUserFunctions(file.Body, "function", func() *hcl.EvalContext {
|
f, diags := hclparse.NewParser().ParseHCL(dt, fn)
|
||||||
return &hcl.EvalContext{
|
if diags.HasErrors() {
|
||||||
Functions: stdlibFunctions,
|
err = diags
|
||||||
}
|
}
|
||||||
})
|
return f, true, err
|
||||||
|
}
|
||||||
|
f, diags := hclparse.NewParser().ParseHCL(dt, fn+".hcl")
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return nil, diags
|
f, diags2 := hclparse.NewParser().ParseJSON(dt, fn+".json")
|
||||||
|
if !diags2.HasErrors() {
|
||||||
|
return f, true, nil
|
||||||
|
}
|
||||||
|
return nil, false, diags
|
||||||
}
|
}
|
||||||
|
return f, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
var sc staticConfig
|
func formatHCLError(err error, files []File) error {
|
||||||
|
if err == nil {
|
||||||
// Decode only variable blocks without interpolation.
|
return nil
|
||||||
if err := hclsimple.Decode(fn, dt, nil, &sc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
diags, ok := err.(hcl.Diagnostics)
|
||||||
// Set all variables to their default value if defined.
|
if !ok {
|
||||||
variables := make(map[string]cty.Value)
|
return err
|
||||||
for _, variable := range sc.Variables {
|
|
||||||
variables[variable.Name] = cty.StringVal(variable.Default)
|
|
||||||
}
|
}
|
||||||
|
for _, d := range diags {
|
||||||
// Override default with values from environment.
|
if d.Severity != hcl.DiagError {
|
||||||
for _, env := range os.Environ() {
|
continue
|
||||||
parts := strings.SplitN(env, "=", 2)
|
}
|
||||||
name, value := parts[0], parts[1]
|
if d.Subject != nil {
|
||||||
if _, ok := variables[name]; ok {
|
var dt []byte
|
||||||
variables[name] = cty.StringVal(value)
|
for _, f := range files {
|
||||||
|
if d.Subject.Filename == f.Name {
|
||||||
|
dt = f.Data
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
src := errdefs.Source{
|
||||||
|
Info: &pb.SourceInfo{
|
||||||
|
Filename: d.Subject.Filename,
|
||||||
|
Data: dt,
|
||||||
|
},
|
||||||
|
Ranges: []*pb.Range{toErrRange(d.Subject)},
|
||||||
|
}
|
||||||
|
err = errdefs.WithSource(err, src)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
functions := make(map[string]function.Function)
|
}
|
||||||
for k, v := range stdlibFunctions {
|
|
||||||
functions[k] = v
|
func toErrRange(in *hcl.Range) *pb.Range {
|
||||||
}
|
return &pb.Range{
|
||||||
for k, v := range userFunctions {
|
Start: pb.Position{Line: int32(in.Start.Line), Character: int32(in.Start.Column)},
|
||||||
functions[k] = v
|
End: pb.Position{Line: int32(in.End.Line), Character: int32(in.End.Column)},
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := &hcl.EvalContext{
|
|
||||||
Variables: variables,
|
|
||||||
Functions: functions,
|
|
||||||
}
|
|
||||||
|
|
||||||
var c Config
|
|
||||||
|
|
||||||
// Decode with variables and functions.
|
|
||||||
if err := hclsimple.Decode(fn, dt, ctx, &c); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &c, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
1439
bake/hcl_test.go
1439
bake/hcl_test.go
File diff suppressed because it is too large
Load Diff
103
bake/hclparser/body.go
Normal file
103
bake/hclparser/body.go
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
package hclparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type filterBody struct {
|
||||||
|
body hcl.Body
|
||||||
|
schema *hcl.BodySchema
|
||||||
|
exclude bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterIncludeBody(body hcl.Body, schema *hcl.BodySchema) hcl.Body {
|
||||||
|
return &filterBody{
|
||||||
|
body: body,
|
||||||
|
schema: schema,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func FilterExcludeBody(body hcl.Body, schema *hcl.BodySchema) hcl.Body {
|
||||||
|
return &filterBody{
|
||||||
|
body: body,
|
||||||
|
schema: schema,
|
||||||
|
exclude: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *filterBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
|
||||||
|
if b.exclude {
|
||||||
|
schema = subtractSchemas(schema, b.schema)
|
||||||
|
} else {
|
||||||
|
schema = intersectSchemas(schema, b.schema)
|
||||||
|
}
|
||||||
|
content, _, diag := b.body.PartialContent(schema)
|
||||||
|
return content, diag
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *filterBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
|
||||||
|
if b.exclude {
|
||||||
|
schema = subtractSchemas(schema, b.schema)
|
||||||
|
} else {
|
||||||
|
schema = intersectSchemas(schema, b.schema)
|
||||||
|
}
|
||||||
|
return b.body.PartialContent(schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *filterBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
|
||||||
|
return b.body.JustAttributes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *filterBody) MissingItemRange() hcl.Range {
|
||||||
|
return b.body.MissingItemRange()
|
||||||
|
}
|
||||||
|
|
||||||
|
func intersectSchemas(a, b *hcl.BodySchema) *hcl.BodySchema {
|
||||||
|
result := &hcl.BodySchema{}
|
||||||
|
for _, blockA := range a.Blocks {
|
||||||
|
for _, blockB := range b.Blocks {
|
||||||
|
if blockA.Type == blockB.Type {
|
||||||
|
result.Blocks = append(result.Blocks, blockA)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, attrA := range a.Attributes {
|
||||||
|
for _, attrB := range b.Attributes {
|
||||||
|
if attrA.Name == attrB.Name {
|
||||||
|
result.Attributes = append(result.Attributes, attrA)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func subtractSchemas(a, b *hcl.BodySchema) *hcl.BodySchema {
|
||||||
|
result := &hcl.BodySchema{}
|
||||||
|
for _, blockA := range a.Blocks {
|
||||||
|
found := false
|
||||||
|
for _, blockB := range b.Blocks {
|
||||||
|
if blockA.Type == blockB.Type {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
result.Blocks = append(result.Blocks, blockA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, attrA := range a.Attributes {
|
||||||
|
found := false
|
||||||
|
for _, attrB := range b.Attributes {
|
||||||
|
if attrA.Name == attrB.Name {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
result.Attributes = append(result.Attributes, attrA)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
145
bake/hclparser/expr.go
Normal file
145
bake/hclparser/expr.go
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
package hclparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func funcCalls(exp hcl.Expression) ([]string, hcl.Diagnostics) {
|
||||||
|
node, ok := exp.(hclsyntax.Node)
|
||||||
|
if !ok {
|
||||||
|
fns, err := jsonFuncCallsRecursive(exp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, wrapErrorDiagnostic("Invalid expression", err, exp.Range().Ptr(), exp.Range().Ptr())
|
||||||
|
}
|
||||||
|
return fns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var funcnames []string
|
||||||
|
hcldiags := hclsyntax.VisitAll(node, func(n hclsyntax.Node) hcl.Diagnostics {
|
||||||
|
if fe, ok := n.(*hclsyntax.FunctionCallExpr); ok {
|
||||||
|
funcnames = append(funcnames, fe.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if hcldiags.HasErrors() {
|
||||||
|
return nil, hcldiags
|
||||||
|
}
|
||||||
|
return funcnames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsonFuncCallsRecursive(exp hcl.Expression) ([]string, error) {
|
||||||
|
je, ok := exp.(jsonExp)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("invalid expression type %T", exp)
|
||||||
|
}
|
||||||
|
m := map[string]struct{}{}
|
||||||
|
for _, e := range elementExpressions(je, exp) {
|
||||||
|
if err := appendJSONFuncCalls(e, m); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arr := make([]string, 0, len(m))
|
||||||
|
for n := range m {
|
||||||
|
arr = append(arr, n)
|
||||||
|
}
|
||||||
|
return arr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendJSONFuncCalls(exp hcl.Expression, m map[string]struct{}) error {
|
||||||
|
v := reflect.ValueOf(exp)
|
||||||
|
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||||
|
return errors.Errorf("invalid json expression kind %T %v", exp, v.Kind())
|
||||||
|
}
|
||||||
|
src := v.Elem().FieldByName("src")
|
||||||
|
if src.IsZero() {
|
||||||
|
return errors.Errorf("%v has no property src", v.Elem().Type())
|
||||||
|
}
|
||||||
|
if src.Kind() != reflect.Interface {
|
||||||
|
return errors.Errorf("%v src is not interface: %v", src.Type(), src.Kind())
|
||||||
|
}
|
||||||
|
src = src.Elem()
|
||||||
|
if src.IsNil() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if src.Kind() == reflect.Ptr {
|
||||||
|
src = src.Elem()
|
||||||
|
}
|
||||||
|
if src.Kind() != reflect.Struct {
|
||||||
|
return errors.Errorf("%v is not struct: %v", src.Type(), src.Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
// hcl/v2/json/ast#stringVal
|
||||||
|
val := src.FieldByName("Value")
|
||||||
|
if !val.IsValid() || val.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rng := src.FieldByName("SrcRange")
|
||||||
|
if rng.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var stringVal struct {
|
||||||
|
Value string
|
||||||
|
SrcRange hcl.Range
|
||||||
|
}
|
||||||
|
|
||||||
|
if !val.Type().AssignableTo(reflect.ValueOf(stringVal.Value).Type()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !rng.Type().AssignableTo(reflect.ValueOf(stringVal.SrcRange).Type()) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// reflect.Set does not work for unexported fields
|
||||||
|
stringVal.Value = *(*string)(unsafe.Pointer(val.UnsafeAddr()))
|
||||||
|
stringVal.SrcRange = *(*hcl.Range)(unsafe.Pointer(rng.UnsafeAddr()))
|
||||||
|
|
||||||
|
expr, diags := hclsyntax.ParseExpression([]byte(stringVal.Value), stringVal.SrcRange.Filename, stringVal.SrcRange.Start)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fns, err := funcCalls(expr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range fns {
|
||||||
|
m[fn] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonExp interface {
|
||||||
|
ExprList() []hcl.Expression
|
||||||
|
ExprMap() []hcl.KeyValuePair
|
||||||
|
}
|
||||||
|
|
||||||
|
func elementExpressions(je jsonExp, exp hcl.Expression) []hcl.Expression {
|
||||||
|
list := je.ExprList()
|
||||||
|
if len(list) != 0 {
|
||||||
|
exp := make([]hcl.Expression, 0, len(list))
|
||||||
|
for _, e := range list {
|
||||||
|
if je, ok := e.(jsonExp); ok {
|
||||||
|
exp = append(exp, elementExpressions(je, e)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exp
|
||||||
|
}
|
||||||
|
kvlist := je.ExprMap()
|
||||||
|
if len(kvlist) != 0 {
|
||||||
|
exp := make([]hcl.Expression, 0, len(kvlist)*2)
|
||||||
|
for _, p := range kvlist {
|
||||||
|
exp = append(exp, p.Key)
|
||||||
|
if je, ok := p.Value.(jsonExp); ok {
|
||||||
|
exp = append(exp, elementExpressions(je, p.Value)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exp
|
||||||
|
}
|
||||||
|
return []hcl.Expression{exp}
|
||||||
|
}
|
||||||
912
bake/hclparser/hclparser.go
Normal file
912
bake/hclparser/hclparser.go
Normal file
@@ -0,0 +1,912 @@
|
|||||||
|
package hclparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"hash/fnv"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/userfunc"
|
||||||
|
"github.com/hashicorp/hcl/v2"
|
||||||
|
"github.com/hashicorp/hcl/v2/gohcl"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
"github.com/zclconf/go-cty/cty/gocty"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Opt struct {
|
||||||
|
LookupVar func(string) (string, bool)
|
||||||
|
Vars map[string]string
|
||||||
|
ValidateLabel func(string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type variable struct {
|
||||||
|
Name string `json:"-" hcl:"name,label"`
|
||||||
|
Default *hcl.Attribute `json:"default,omitempty" hcl:"default,optional"`
|
||||||
|
Body hcl.Body `json:"-" hcl:",body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type functionDef struct {
|
||||||
|
Name string `json:"-" hcl:"name,label"`
|
||||||
|
Params *hcl.Attribute `json:"params,omitempty" hcl:"params"`
|
||||||
|
Variadic *hcl.Attribute `json:"variadic_param,omitempty" hcl:"variadic_params"`
|
||||||
|
Result *hcl.Attribute `json:"result,omitempty" hcl:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type inputs struct {
|
||||||
|
Variables []*variable `hcl:"variable,block"`
|
||||||
|
Functions []*functionDef `hcl:"function,block"`
|
||||||
|
|
||||||
|
Remain hcl.Body `json:"-" hcl:",remain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type parser struct {
|
||||||
|
opt Opt
|
||||||
|
|
||||||
|
vars map[string]*variable
|
||||||
|
attrs map[string]*hcl.Attribute
|
||||||
|
funcs map[string]*functionDef
|
||||||
|
|
||||||
|
blocks map[string]map[string][]*hcl.Block
|
||||||
|
blockValues map[*hcl.Block][]reflect.Value
|
||||||
|
blockEvalCtx map[*hcl.Block][]*hcl.EvalContext
|
||||||
|
blockNames map[*hcl.Block][]string
|
||||||
|
blockTypes map[string]reflect.Type
|
||||||
|
|
||||||
|
ectx *hcl.EvalContext
|
||||||
|
|
||||||
|
progressV map[uint64]struct{}
|
||||||
|
progressF map[uint64]struct{}
|
||||||
|
progressB map[uint64]map[string]struct{}
|
||||||
|
doneB map[uint64]map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type WithEvalContexts interface {
|
||||||
|
GetEvalContexts(base *hcl.EvalContext, block *hcl.Block, loadDeps func(hcl.Expression) hcl.Diagnostics) ([]*hcl.EvalContext, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type WithGetName interface {
|
||||||
|
GetName(ectx *hcl.EvalContext, block *hcl.Block, loadDeps func(hcl.Expression) hcl.Diagnostics) (string, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var errUndefined = errors.New("undefined")
|
||||||
|
|
||||||
|
func (p *parser) loadDeps(ectx *hcl.EvalContext, exp hcl.Expression, exclude map[string]struct{}, allowMissing bool) hcl.Diagnostics {
|
||||||
|
fns, hcldiags := funcCalls(exp)
|
||||||
|
if hcldiags.HasErrors() {
|
||||||
|
return hcldiags
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range fns {
|
||||||
|
if err := p.resolveFunction(ectx, fn); err != nil {
|
||||||
|
if allowMissing && errors.Is(err, errUndefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return wrapErrorDiagnostic("Invalid expression", err, exp.Range().Ptr(), exp.Range().Ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range exp.Variables() {
|
||||||
|
if _, ok := exclude[v.RootName()]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := p.blockTypes[v.RootName()]; ok {
|
||||||
|
blockType := v.RootName()
|
||||||
|
|
||||||
|
split := v.SimpleSplit().Rel
|
||||||
|
if len(split) == 0 {
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid expression",
|
||||||
|
Detail: fmt.Sprintf("cannot access %s as a variable", blockType),
|
||||||
|
Subject: exp.Range().Ptr(),
|
||||||
|
Context: exp.Range().Ptr(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
blockName, ok := split[0].(hcl.TraverseAttr)
|
||||||
|
if !ok {
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid expression",
|
||||||
|
Detail: fmt.Sprintf("cannot traverse %s without attribute", blockType),
|
||||||
|
Subject: exp.Range().Ptr(),
|
||||||
|
Context: exp.Range().Ptr(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
blocks := p.blocks[blockType][blockName.Name]
|
||||||
|
if len(blocks) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var target *hcl.BodySchema
|
||||||
|
if len(split) > 1 {
|
||||||
|
if attr, ok := split[1].(hcl.TraverseAttr); ok {
|
||||||
|
target = &hcl.BodySchema{
|
||||||
|
Attributes: []hcl.AttributeSchema{{Name: attr.Name}},
|
||||||
|
Blocks: []hcl.BlockHeaderSchema{{Type: attr.Name}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, block := range blocks {
|
||||||
|
if err := p.resolveBlock(block, target); err != nil {
|
||||||
|
if allowMissing && errors.Is(err, errUndefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return wrapErrorDiagnostic("Invalid expression", err, exp.Range().Ptr(), exp.Range().Ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := p.resolveValue(ectx, v.RootName()); err != nil {
|
||||||
|
if allowMissing && errors.Is(err, errUndefined) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return wrapErrorDiagnostic("Invalid expression", err, exp.Range().Ptr(), exp.Range().Ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveFunction forces evaluation of a function, storing the result into the
|
||||||
|
// parser.
|
||||||
|
func (p *parser) resolveFunction(ectx *hcl.EvalContext, name string) error {
|
||||||
|
if _, ok := p.ectx.Functions[name]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, ok := ectx.Functions[name]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f, ok := p.funcs[name]
|
||||||
|
if !ok {
|
||||||
|
return errors.Wrapf(errUndefined, "function %q does not exist", name)
|
||||||
|
}
|
||||||
|
if _, ok := p.progressF[key(ectx, name)]; ok {
|
||||||
|
return errors.Errorf("function cycle not allowed for %s", name)
|
||||||
|
}
|
||||||
|
p.progressF[key(ectx, name)] = struct{}{}
|
||||||
|
|
||||||
|
if f.Result == nil {
|
||||||
|
return errors.Errorf("empty result not allowed for %s", name)
|
||||||
|
}
|
||||||
|
if f.Params == nil {
|
||||||
|
return errors.Errorf("empty params not allowed for %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
paramExprs, paramsDiags := hcl.ExprList(f.Params.Expr)
|
||||||
|
if paramsDiags.HasErrors() {
|
||||||
|
return paramsDiags
|
||||||
|
}
|
||||||
|
var diags hcl.Diagnostics
|
||||||
|
params := map[string]struct{}{}
|
||||||
|
for _, paramExpr := range paramExprs {
|
||||||
|
param := hcl.ExprAsKeyword(paramExpr)
|
||||||
|
if param == "" {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid param element",
|
||||||
|
Detail: "Each parameter name must be an identifier.",
|
||||||
|
Subject: paramExpr.Range().Ptr(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
params[param] = struct{}{}
|
||||||
|
}
|
||||||
|
var variadic hcl.Expression
|
||||||
|
if f.Variadic != nil {
|
||||||
|
variadic = f.Variadic.Expr
|
||||||
|
param := hcl.ExprAsKeyword(variadic)
|
||||||
|
if param == "" {
|
||||||
|
diags = append(diags, &hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid param element",
|
||||||
|
Detail: "Each parameter name must be an identifier.",
|
||||||
|
Subject: f.Variadic.Range.Ptr(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
params[param] = struct{}{}
|
||||||
|
}
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
if diags := p.loadDeps(p.ectx, f.Result.Expr, params, false); diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
v, diags := userfunc.NewFunction(f.Params.Expr, variadic, f.Result.Expr, func() *hcl.EvalContext {
|
||||||
|
return p.ectx
|
||||||
|
})
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
p.ectx.Functions[name] = v
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveValue forces evaluation of a named value, storing the result into the
|
||||||
|
// parser.
|
||||||
|
func (p *parser) resolveValue(ectx *hcl.EvalContext, name string) (err error) {
|
||||||
|
if _, ok := p.ectx.Variables[name]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, ok := ectx.Variables[name]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, ok := p.progressV[key(ectx, name)]; ok {
|
||||||
|
return errors.Errorf("variable cycle not allowed for %s", name)
|
||||||
|
}
|
||||||
|
p.progressV[key(ectx, name)] = struct{}{}
|
||||||
|
|
||||||
|
var v *cty.Value
|
||||||
|
defer func() {
|
||||||
|
if v != nil {
|
||||||
|
p.ectx.Variables[name] = *v
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
def, ok := p.attrs[name]
|
||||||
|
if _, builtin := p.opt.Vars[name]; !ok && !builtin {
|
||||||
|
vr, ok := p.vars[name]
|
||||||
|
if !ok {
|
||||||
|
return errors.Wrapf(errUndefined, "variable %q does not exist", name)
|
||||||
|
}
|
||||||
|
def = vr.Default
|
||||||
|
ectx = p.ectx
|
||||||
|
}
|
||||||
|
|
||||||
|
if def == nil {
|
||||||
|
val, ok := p.opt.Vars[name]
|
||||||
|
if !ok {
|
||||||
|
val, _ = p.opt.LookupVar(name)
|
||||||
|
}
|
||||||
|
vv := cty.StringVal(val)
|
||||||
|
v = &vv
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if diags := p.loadDeps(ectx, def.Expr, nil, true); diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
vv, diags := def.Expr.Value(ectx)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
_, isVar := p.vars[name]
|
||||||
|
|
||||||
|
if envv, ok := p.opt.LookupVar(name); ok && isVar {
|
||||||
|
switch {
|
||||||
|
case vv.Type().Equals(cty.Bool):
|
||||||
|
b, err := strconv.ParseBool(envv)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to parse %s as bool", name)
|
||||||
|
}
|
||||||
|
vv = cty.BoolVal(b)
|
||||||
|
case vv.Type().Equals(cty.String), vv.Type().Equals(cty.DynamicPseudoType):
|
||||||
|
vv = cty.StringVal(envv)
|
||||||
|
case vv.Type().Equals(cty.Number):
|
||||||
|
n, err := strconv.ParseFloat(envv, 64)
|
||||||
|
if err == nil && (math.IsNaN(n) || math.IsInf(n, 0)) {
|
||||||
|
err = errors.Errorf("invalid number value")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to parse %s as number", name)
|
||||||
|
}
|
||||||
|
vv = cty.NumberVal(big.NewFloat(n))
|
||||||
|
default:
|
||||||
|
// TODO: support lists with csv values
|
||||||
|
return errors.Errorf("unsupported type %s for variable %s", vv.Type().FriendlyName(), name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = &vv
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveBlock force evaluates a block, storing the result in the parser. If a
|
||||||
|
// target schema is provided, only the attributes and blocks present in the
|
||||||
|
// schema will be evaluated.
|
||||||
|
func (p *parser) resolveBlock(block *hcl.Block, target *hcl.BodySchema) (err error) {
|
||||||
|
// prepare the variable map for this type
|
||||||
|
if _, ok := p.ectx.Variables[block.Type]; !ok {
|
||||||
|
p.ectx.Variables[block.Type] = cty.MapValEmpty(cty.Map(cty.String))
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare the output destination and evaluation context
|
||||||
|
t, ok := p.blockTypes[block.Type]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var outputs []reflect.Value
|
||||||
|
var ectxs []*hcl.EvalContext
|
||||||
|
if prev, ok := p.blockValues[block]; ok {
|
||||||
|
outputs = prev
|
||||||
|
ectxs = p.blockEvalCtx[block]
|
||||||
|
} else {
|
||||||
|
if v, ok := reflect.New(t).Interface().(WithEvalContexts); ok {
|
||||||
|
ectxs, err = v.GetEvalContexts(p.ectx, block, func(expr hcl.Expression) hcl.Diagnostics {
|
||||||
|
return p.loadDeps(p.ectx, expr, nil, true)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, ectx := range ectxs {
|
||||||
|
if ectx != p.ectx && ectx.Parent() != p.ectx {
|
||||||
|
return errors.Errorf("EvalContext must return a context with the correct parent")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ectxs = append([]*hcl.EvalContext{}, p.ectx)
|
||||||
|
}
|
||||||
|
for range ectxs {
|
||||||
|
outputs = append(outputs, reflect.New(t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.blockValues[block] = outputs
|
||||||
|
p.blockEvalCtx[block] = ectxs
|
||||||
|
|
||||||
|
for i, output := range outputs {
|
||||||
|
target := target
|
||||||
|
ectx := ectxs[i]
|
||||||
|
name := block.Labels[0]
|
||||||
|
if names, ok := p.blockNames[block]; ok {
|
||||||
|
name = names[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := p.doneB[key(block, ectx)]; !ok {
|
||||||
|
p.doneB[key(block, ectx)] = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
if _, ok := p.progressB[key(block, ectx)]; !ok {
|
||||||
|
p.progressB[key(block, ectx)] = map[string]struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target != nil {
|
||||||
|
// filter out attributes and blocks that are already evaluated
|
||||||
|
original := target
|
||||||
|
target = &hcl.BodySchema{}
|
||||||
|
for _, a := range original.Attributes {
|
||||||
|
if _, ok := p.doneB[key(block, ectx)][a.Name]; !ok {
|
||||||
|
target.Attributes = append(target.Attributes, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, b := range original.Blocks {
|
||||||
|
if _, ok := p.doneB[key(block, ectx)][b.Type]; !ok {
|
||||||
|
target.Blocks = append(target.Blocks, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(target.Attributes) == 0 && len(target.Blocks) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if target != nil {
|
||||||
|
// detect reference cycles
|
||||||
|
for _, a := range target.Attributes {
|
||||||
|
if _, ok := p.progressB[key(block, ectx)][a.Name]; ok {
|
||||||
|
return errors.Errorf("reference cycle not allowed for %s.%s.%s", block.Type, name, a.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, b := range target.Blocks {
|
||||||
|
if _, ok := p.progressB[key(block, ectx)][b.Type]; ok {
|
||||||
|
return errors.Errorf("reference cycle not allowed for %s.%s.%s", block.Type, name, b.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, a := range target.Attributes {
|
||||||
|
p.progressB[key(block, ectx)][a.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, b := range target.Blocks {
|
||||||
|
p.progressB[key(block, ectx)][b.Type] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a filtered body that contains only the target properties
|
||||||
|
body := func() hcl.Body {
|
||||||
|
if target != nil {
|
||||||
|
return FilterIncludeBody(block.Body, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := &hcl.BodySchema{}
|
||||||
|
for k := range p.doneB[key(block, ectx)] {
|
||||||
|
filter.Attributes = append(filter.Attributes, hcl.AttributeSchema{Name: k})
|
||||||
|
filter.Blocks = append(filter.Blocks, hcl.BlockHeaderSchema{Type: k})
|
||||||
|
}
|
||||||
|
return FilterExcludeBody(block.Body, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// load dependencies from all targeted properties
|
||||||
|
schema, _ := gohcl.ImpliedBodySchema(reflect.New(t).Interface())
|
||||||
|
content, _, diag := body().PartialContent(schema)
|
||||||
|
if diag.HasErrors() {
|
||||||
|
return diag
|
||||||
|
}
|
||||||
|
for _, a := range content.Attributes {
|
||||||
|
diag := p.loadDeps(ectx, a.Expr, nil, true)
|
||||||
|
if diag.HasErrors() {
|
||||||
|
return diag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, b := range content.Blocks {
|
||||||
|
err := p.resolveBlock(b, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode!
|
||||||
|
diag = gohcl.DecodeBody(body(), ectx, output.Interface())
|
||||||
|
if diag.HasErrors() {
|
||||||
|
return diag
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark all targeted properties as done
|
||||||
|
for _, a := range content.Attributes {
|
||||||
|
p.doneB[key(block, ectx)][a.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, b := range content.Blocks {
|
||||||
|
p.doneB[key(block, ectx)][b.Type] = struct{}{}
|
||||||
|
}
|
||||||
|
if target != nil {
|
||||||
|
for _, a := range target.Attributes {
|
||||||
|
p.doneB[key(block, ectx)][a.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
for _, b := range target.Blocks {
|
||||||
|
p.doneB[key(block, ectx)][b.Type] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the result into the evaluation context (so it can be referenced)
|
||||||
|
outputType, err := gocty.ImpliedType(output.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
outputValue, err := gocty.ToCtyValue(output.Interface(), outputType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var m map[string]cty.Value
|
||||||
|
if m2, ok := p.ectx.Variables[block.Type]; ok {
|
||||||
|
m = m2.AsValueMap()
|
||||||
|
}
|
||||||
|
if m == nil {
|
||||||
|
m = map[string]cty.Value{}
|
||||||
|
}
|
||||||
|
m[name] = outputValue
|
||||||
|
p.ectx.Variables[block.Type] = cty.MapVal(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveBlockNames returns the names of the block, calling resolveBlock to
|
||||||
|
// evaluate any label fields to correctly resolve the name.
|
||||||
|
func (p *parser) resolveBlockNames(block *hcl.Block) ([]string, error) {
|
||||||
|
if names, ok := p.blockNames[block]; ok {
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.resolveBlock(block, &hcl.BodySchema{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
names := make([]string, 0, len(p.blockValues[block]))
|
||||||
|
for i, val := range p.blockValues[block] {
|
||||||
|
ectx := p.blockEvalCtx[block][i]
|
||||||
|
|
||||||
|
name := block.Labels[0]
|
||||||
|
if err := p.opt.ValidateLabel(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := val.Interface().(WithGetName); ok {
|
||||||
|
var err error
|
||||||
|
name, err = v.GetName(ectx, block, func(expr hcl.Expression) hcl.Diagnostics {
|
||||||
|
return p.loadDeps(ectx, expr, nil, true)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := p.opt.ValidateLabel(name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setName(val, name)
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
found := map[string]struct{}{}
|
||||||
|
for _, name := range names {
|
||||||
|
if _, ok := found[name]; ok {
|
||||||
|
return nil, errors.Errorf("duplicate name %q", name)
|
||||||
|
}
|
||||||
|
found[name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.blockNames[block] = names
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(b hcl.Body, opt Opt, val interface{}) (map[string]map[string][]string, hcl.Diagnostics) {
|
||||||
|
reserved := map[string]struct{}{}
|
||||||
|
schema, _ := gohcl.ImpliedBodySchema(val)
|
||||||
|
|
||||||
|
for _, bs := range schema.Blocks {
|
||||||
|
reserved[bs.Type] = struct{}{}
|
||||||
|
}
|
||||||
|
for k := range opt.Vars {
|
||||||
|
reserved[k] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var defs inputs
|
||||||
|
if err := gohcl.DecodeBody(b, nil, &defs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defsSchema, _ := gohcl.ImpliedBodySchema(defs)
|
||||||
|
|
||||||
|
if opt.LookupVar == nil {
|
||||||
|
opt.LookupVar = func(string) (string, bool) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opt.ValidateLabel == nil {
|
||||||
|
opt.ValidateLabel = func(string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := &parser{
|
||||||
|
opt: opt,
|
||||||
|
|
||||||
|
vars: map[string]*variable{},
|
||||||
|
attrs: map[string]*hcl.Attribute{},
|
||||||
|
funcs: map[string]*functionDef{},
|
||||||
|
|
||||||
|
blocks: map[string]map[string][]*hcl.Block{},
|
||||||
|
blockValues: map[*hcl.Block][]reflect.Value{},
|
||||||
|
blockEvalCtx: map[*hcl.Block][]*hcl.EvalContext{},
|
||||||
|
blockNames: map[*hcl.Block][]string{},
|
||||||
|
blockTypes: map[string]reflect.Type{},
|
||||||
|
ectx: &hcl.EvalContext{
|
||||||
|
Variables: map[string]cty.Value{},
|
||||||
|
Functions: Stdlib(),
|
||||||
|
},
|
||||||
|
|
||||||
|
progressV: map[uint64]struct{}{},
|
||||||
|
progressF: map[uint64]struct{}{},
|
||||||
|
progressB: map[uint64]map[string]struct{}{},
|
||||||
|
doneB: map[uint64]map[string]struct{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range defs.Variables {
|
||||||
|
// TODO: validate name
|
||||||
|
if _, ok := reserved[v.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.vars[v.Name] = v
|
||||||
|
}
|
||||||
|
for _, v := range defs.Functions {
|
||||||
|
// TODO: validate name
|
||||||
|
if _, ok := reserved[v.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.funcs[v.Name] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
content, b, diags := b.PartialContent(schema)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks, b, diags := b.PartialContent(defsSchema)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs, diags := b.JustAttributes()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
if d := removeAttributesDiags(diags, reserved, p.vars, attrs); len(d) > 0 {
|
||||||
|
return nil, d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range attrs {
|
||||||
|
if _, ok := reserved[v.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.attrs[v.Name] = v
|
||||||
|
}
|
||||||
|
delete(p.attrs, "function")
|
||||||
|
|
||||||
|
for k := range p.opt.Vars {
|
||||||
|
_ = p.resolveValue(p.ectx, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Ptr(),
|
||||||
|
Context: a.Range.Ptr(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range p.vars {
|
||||||
|
if err := p.resolveValue(p.ectx, k); err != nil {
|
||||||
|
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
r := p.vars[k].Body.MissingItemRange()
|
||||||
|
return nil, wrapErrorDiagnostic("Invalid value", err, &r, &r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range p.funcs {
|
||||||
|
if err := p.resolveFunction(p.ectx, k); err != nil {
|
||||||
|
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
var subject *hcl.Range
|
||||||
|
var context *hcl.Range
|
||||||
|
if p.funcs[k].Params != nil {
|
||||||
|
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].Ptr()
|
||||||
|
context = block.DefRange.Ptr()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, wrapErrorDiagnostic("Invalid function", err, subject, context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type value struct {
|
||||||
|
reflect.Value
|
||||||
|
idx int
|
||||||
|
}
|
||||||
|
type field struct {
|
||||||
|
idx int
|
||||||
|
typ reflect.Type
|
||||||
|
values map[string]value
|
||||||
|
}
|
||||||
|
types := map[string]field{}
|
||||||
|
renamed := map[string]map[string][]string{}
|
||||||
|
vt := reflect.ValueOf(val).Elem().Type()
|
||||||
|
for i := 0; i < vt.NumField(); i++ {
|
||||||
|
tags := strings.Split(vt.Field(i).Tag.Get("hcl"), ",")
|
||||||
|
|
||||||
|
p.blockTypes[tags[0]] = vt.Field(i).Type.Elem().Elem()
|
||||||
|
types[tags[0]] = field{
|
||||||
|
idx: i,
|
||||||
|
typ: vt.Field(i).Type,
|
||||||
|
values: make(map[string]value),
|
||||||
|
}
|
||||||
|
renamed[tags[0]] = map[string][]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpBlocks := map[string]map[string][]*hcl.Block{}
|
||||||
|
for _, b := range content.Blocks {
|
||||||
|
if len(b.Labels) == 0 || len(b.Labels) > 1 {
|
||||||
|
return nil, hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid block",
|
||||||
|
Detail: fmt.Sprintf("invalid block label: %v", b.Labels),
|
||||||
|
Subject: &b.LabelRanges[0],
|
||||||
|
Context: &b.LabelRanges[0],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bm, ok := tmpBlocks[b.Type]
|
||||||
|
if !ok {
|
||||||
|
bm = map[string][]*hcl.Block{}
|
||||||
|
tmpBlocks[b.Type] = bm
|
||||||
|
}
|
||||||
|
|
||||||
|
names, err := p.resolveBlockNames(b)
|
||||||
|
if err != nil {
|
||||||
|
return nil, wrapErrorDiagnostic("Invalid name", err, &b.LabelRanges[0], &b.LabelRanges[0])
|
||||||
|
}
|
||||||
|
for _, name := range names {
|
||||||
|
bm[name] = append(bm[name], b)
|
||||||
|
renamed[b.Type][b.Labels[0]] = append(renamed[b.Type][b.Labels[0]], name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.blocks = tmpBlocks
|
||||||
|
|
||||||
|
diags = hcl.Diagnostics{}
|
||||||
|
for _, b := range content.Blocks {
|
||||||
|
b := b
|
||||||
|
v := reflect.ValueOf(val)
|
||||||
|
|
||||||
|
err := p.resolveBlock(b, nil)
|
||||||
|
if err != nil {
|
||||||
|
if diag, ok := err.(hcl.Diagnostics); ok {
|
||||||
|
if diag.HasErrors() {
|
||||||
|
diags = append(diags, diag...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, wrapErrorDiagnostic("Invalid block", err, b.LabelRanges[0].Ptr(), b.DefRange.Ptr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vvs := p.blockValues[b]
|
||||||
|
for _, vv := range vvs {
|
||||||
|
t := types[b.Type]
|
||||||
|
lblIndex, lblExists := getNameIndex(vv)
|
||||||
|
lblName, _ := getName(vv)
|
||||||
|
oldValue, exists := t.values[lblName]
|
||||||
|
if !exists && lblExists {
|
||||||
|
if v.Elem().Field(t.idx).Type().Kind() == reflect.Slice {
|
||||||
|
for i := 0; i < v.Elem().Field(t.idx).Len(); i++ {
|
||||||
|
if lblName == v.Elem().Field(t.idx).Index(i).Elem().Field(lblIndex).String() {
|
||||||
|
exists = true
|
||||||
|
oldValue = value{Value: v.Elem().Field(t.idx).Index(i), idx: i}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
if m := oldValue.Value.MethodByName("Merge"); m.IsValid() {
|
||||||
|
m.Call([]reflect.Value{vv})
|
||||||
|
} else {
|
||||||
|
v.Elem().Field(t.idx).Index(oldValue.idx).Set(vv)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
slice := v.Elem().Field(t.idx)
|
||||||
|
if slice.IsNil() {
|
||||||
|
slice = reflect.New(t.typ).Elem()
|
||||||
|
}
|
||||||
|
t.values[lblName] = value{Value: vv, idx: slice.Len()}
|
||||||
|
v.Elem().Field(t.idx).Set(reflect.Append(slice, vv))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range p.attrs {
|
||||||
|
if err := p.resolveValue(p.ectx, k); err != nil {
|
||||||
|
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||||
|
return nil, diags
|
||||||
|
}
|
||||||
|
return nil, wrapErrorDiagnostic("Invalid attribute", err, &p.attrs[k].Range, &p.attrs[k].Range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return renamed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// wrapErrorDiagnostic wraps an error into a hcl.Diagnostics object.
|
||||||
|
// If the error is already an hcl.Diagnostics object, it is returned as is.
|
||||||
|
func wrapErrorDiagnostic(message string, err error, subject *hcl.Range, context *hcl.Range) hcl.Diagnostics {
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *hcl.Diagnostic:
|
||||||
|
return hcl.Diagnostics{err}
|
||||||
|
case hcl.Diagnostics:
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: message,
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: subject,
|
||||||
|
Context: context,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setName(v reflect.Value, name string) {
|
||||||
|
numFields := v.Elem().Type().NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
parts := strings.Split(v.Elem().Type().Field(i).Tag.Get("hcl"), ",")
|
||||||
|
for _, t := range parts[1:] {
|
||||||
|
if t == "label" {
|
||||||
|
v.Elem().Field(i).Set(reflect.ValueOf(name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getName(v reflect.Value) (string, bool) {
|
||||||
|
numFields := v.Elem().Type().NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
parts := strings.Split(v.Elem().Type().Field(i).Tag.Get("hcl"), ",")
|
||||||
|
for _, t := range parts[1:] {
|
||||||
|
if t == "label" {
|
||||||
|
return v.Elem().Field(i).String(), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNameIndex(v reflect.Value) (int, bool) {
|
||||||
|
numFields := v.Elem().Type().NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
parts := strings.Split(v.Elem().Type().Field(i).Tag.Get("hcl"), ",")
|
||||||
|
for _, t := range parts[1:] {
|
||||||
|
if t == "label" {
|
||||||
|
return i, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// https://github.com/docker/buildx/pull/541
|
||||||
|
if d.Detail == "Blocks are not allowed here." {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for r := range reserved {
|
||||||
|
// JSON body objects don't handle repeated blocks like HCL but
|
||||||
|
// reserved name attributes should be allowed when multi bodies are merged.
|
||||||
|
// https://github.com/hashicorp/hcl/blob/main/json/spec.md#blocks
|
||||||
|
if strings.HasPrefix(d.Detail, fmt.Sprintf(`Argument "%s" was already set at `, r)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for v := range vars {
|
||||||
|
// Do the same for global variables
|
||||||
|
if strings.HasPrefix(d.Detail, fmt.Sprintf(`Argument "%s" was already set at `, v)) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fdiags
|
||||||
|
}
|
||||||
|
|
||||||
|
// key returns a unique hash for the given values
|
||||||
|
func key(ks ...any) uint64 {
|
||||||
|
hash := fnv.New64a()
|
||||||
|
for _, k := range ks {
|
||||||
|
v := reflect.ValueOf(k)
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
hash.Write([]byte(v.String()))
|
||||||
|
case reflect.Pointer:
|
||||||
|
ptr := reflect.ValueOf(k).Pointer()
|
||||||
|
binary.Write(hash, binary.LittleEndian, uint64(ptr))
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown key kind %s", v.Kind().String()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hash.Sum64()
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
182
bake/hclparser/stdlib.go
Normal file
182
bake/hclparser/stdlib.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
package hclparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-cty-funcs/cidr"
|
||||||
|
"github.com/hashicorp/go-cty-funcs/crypto"
|
||||||
|
"github.com/hashicorp/go-cty-funcs/encoding"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
var stdlibFunctions = map[string]function.Function{
|
||||||
|
"absolute": stdlib.AbsoluteFunc,
|
||||||
|
"add": stdlib.AddFunc,
|
||||||
|
"and": stdlib.AndFunc,
|
||||||
|
"base64decode": encoding.Base64DecodeFunc,
|
||||||
|
"base64encode": encoding.Base64EncodeFunc,
|
||||||
|
"bcrypt": crypto.BcryptFunc,
|
||||||
|
"byteslen": stdlib.BytesLenFunc,
|
||||||
|
"bytesslice": stdlib.BytesSliceFunc,
|
||||||
|
"can": tryfunc.CanFunc,
|
||||||
|
"ceil": stdlib.CeilFunc,
|
||||||
|
"chomp": stdlib.ChompFunc,
|
||||||
|
"chunklist": stdlib.ChunklistFunc,
|
||||||
|
"cidrhost": cidr.HostFunc,
|
||||||
|
"cidrnetmask": cidr.NetmaskFunc,
|
||||||
|
"cidrsubnet": cidr.SubnetFunc,
|
||||||
|
"cidrsubnets": cidr.SubnetsFunc,
|
||||||
|
"coalesce": stdlib.CoalesceFunc,
|
||||||
|
"coalescelist": stdlib.CoalesceListFunc,
|
||||||
|
"compact": stdlib.CompactFunc,
|
||||||
|
"concat": stdlib.ConcatFunc,
|
||||||
|
"contains": stdlib.ContainsFunc,
|
||||||
|
"convert": typeexpr.ConvertFunc,
|
||||||
|
"csvdecode": stdlib.CSVDecodeFunc,
|
||||||
|
"distinct": stdlib.DistinctFunc,
|
||||||
|
"divide": stdlib.DivideFunc,
|
||||||
|
"element": stdlib.ElementFunc,
|
||||||
|
"equal": stdlib.EqualFunc,
|
||||||
|
"flatten": stdlib.FlattenFunc,
|
||||||
|
"floor": stdlib.FloorFunc,
|
||||||
|
"format": stdlib.FormatFunc,
|
||||||
|
"formatdate": stdlib.FormatDateFunc,
|
||||||
|
"formatlist": stdlib.FormatListFunc,
|
||||||
|
"greaterthan": stdlib.GreaterThanFunc,
|
||||||
|
"greaterthanorequalto": stdlib.GreaterThanOrEqualToFunc,
|
||||||
|
"hasindex": stdlib.HasIndexFunc,
|
||||||
|
"indent": stdlib.IndentFunc,
|
||||||
|
"index": stdlib.IndexFunc,
|
||||||
|
"indexof": indexOfFunc,
|
||||||
|
"int": stdlib.IntFunc,
|
||||||
|
"join": stdlib.JoinFunc,
|
||||||
|
"jsondecode": stdlib.JSONDecodeFunc,
|
||||||
|
"jsonencode": stdlib.JSONEncodeFunc,
|
||||||
|
"keys": stdlib.KeysFunc,
|
||||||
|
"length": stdlib.LengthFunc,
|
||||||
|
"lessthan": stdlib.LessThanFunc,
|
||||||
|
"lessthanorequalto": stdlib.LessThanOrEqualToFunc,
|
||||||
|
"log": stdlib.LogFunc,
|
||||||
|
"lookup": stdlib.LookupFunc,
|
||||||
|
"lower": stdlib.LowerFunc,
|
||||||
|
"max": stdlib.MaxFunc,
|
||||||
|
"md5": crypto.Md5Func,
|
||||||
|
"merge": stdlib.MergeFunc,
|
||||||
|
"min": stdlib.MinFunc,
|
||||||
|
"modulo": stdlib.ModuloFunc,
|
||||||
|
"multiply": stdlib.MultiplyFunc,
|
||||||
|
"negate": stdlib.NegateFunc,
|
||||||
|
"not": stdlib.NotFunc,
|
||||||
|
"notequal": stdlib.NotEqualFunc,
|
||||||
|
"or": stdlib.OrFunc,
|
||||||
|
"parseint": stdlib.ParseIntFunc,
|
||||||
|
"pow": stdlib.PowFunc,
|
||||||
|
"range": stdlib.RangeFunc,
|
||||||
|
"regex_replace": stdlib.RegexReplaceFunc,
|
||||||
|
"regex": stdlib.RegexFunc,
|
||||||
|
"regexall": stdlib.RegexAllFunc,
|
||||||
|
"replace": stdlib.ReplaceFunc,
|
||||||
|
"reverse": stdlib.ReverseFunc,
|
||||||
|
"reverselist": stdlib.ReverseListFunc,
|
||||||
|
"rsadecrypt": crypto.RsaDecryptFunc,
|
||||||
|
"sethaselement": stdlib.SetHasElementFunc,
|
||||||
|
"setintersection": stdlib.SetIntersectionFunc,
|
||||||
|
"setproduct": stdlib.SetProductFunc,
|
||||||
|
"setsubtract": stdlib.SetSubtractFunc,
|
||||||
|
"setsymmetricdifference": stdlib.SetSymmetricDifferenceFunc,
|
||||||
|
"setunion": stdlib.SetUnionFunc,
|
||||||
|
"sha1": crypto.Sha1Func,
|
||||||
|
"sha256": crypto.Sha256Func,
|
||||||
|
"sha512": crypto.Sha512Func,
|
||||||
|
"signum": stdlib.SignumFunc,
|
||||||
|
"slice": stdlib.SliceFunc,
|
||||||
|
"sort": stdlib.SortFunc,
|
||||||
|
"split": stdlib.SplitFunc,
|
||||||
|
"strlen": stdlib.StrlenFunc,
|
||||||
|
"substr": stdlib.SubstrFunc,
|
||||||
|
"subtract": stdlib.SubtractFunc,
|
||||||
|
"timeadd": stdlib.TimeAddFunc,
|
||||||
|
"timestamp": timestampFunc,
|
||||||
|
"title": stdlib.TitleFunc,
|
||||||
|
"trim": stdlib.TrimFunc,
|
||||||
|
"trimprefix": stdlib.TrimPrefixFunc,
|
||||||
|
"trimspace": stdlib.TrimSpaceFunc,
|
||||||
|
"trimsuffix": stdlib.TrimSuffixFunc,
|
||||||
|
"try": tryfunc.TryFunc,
|
||||||
|
"upper": stdlib.UpperFunc,
|
||||||
|
"urlencode": encoding.URLEncodeFunc,
|
||||||
|
"uuidv4": uuid.V4Func,
|
||||||
|
"uuidv5": uuid.V5Func,
|
||||||
|
"values": stdlib.ValuesFunc,
|
||||||
|
"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.
|
||||||
|
var timestampFunc = function.New(&function.Spec{
|
||||||
|
Params: []function.Parameter{},
|
||||||
|
Type: function.StaticReturnType(cty.String),
|
||||||
|
Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
|
||||||
|
return cty.StringVal(time.Now().UTC().Format(time.RFC3339)), nil
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
func Stdlib() map[string]function.Function {
|
||||||
|
funcs := make(map[string]function.Function, len(stdlibFunctions))
|
||||||
|
for k, v := range stdlibFunctions {
|
||||||
|
funcs[k] = v
|
||||||
|
}
|
||||||
|
return funcs
|
||||||
|
}
|
||||||
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
223
bake/remote.go
Normal file
223
bake/remote.go
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
package bake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
|
"github.com/docker/buildx/driver"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/client/llb"
|
||||||
|
"github.com/moby/buildkit/frontend/dockerui"
|
||||||
|
gwclient "github.com/moby/buildkit/frontend/gateway/client"
|
||||||
|
"github.com/moby/buildkit/session"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Input struct {
|
||||||
|
State *llb.State
|
||||||
|
URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ReadRemoteFiles(ctx context.Context, nodes []builder.Node, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
|
||||||
|
var sessions []session.Attachable
|
||||||
|
var filename string
|
||||||
|
|
||||||
|
st, ok := dockerui.DetectGitContext(url, false)
|
||||||
|
if ok {
|
||||||
|
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)
|
||||||
|
if !ok {
|
||||||
|
return nil, nil, errors.Errorf("not url context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inp := &Input{State: st, URL: url}
|
||||||
|
var files []File
|
||||||
|
|
||||||
|
var node *builder.Node
|
||||||
|
for i, n := range nodes {
|
||||||
|
if n.Err == nil {
|
||||||
|
node = &nodes[i]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node == nil {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := driver.Boot(ctx, ctx, node.Driver, pw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, done := progress.NewChannel(pw)
|
||||||
|
defer func() { <-done }()
|
||||||
|
_, 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
|
||||||
|
}
|
||||||
|
res, err := c.Solve(ctx, gwclient.SolveRequest{
|
||||||
|
Definition: def.ToPB(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := res.SingleRef()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if filename != "" {
|
||||||
|
files, err = filesFromURLRef(ctx, c, ref, inp, filename, names)
|
||||||
|
} else {
|
||||||
|
files, err = filesFromRef(ctx, ref, names)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}, ch)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, inp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isArchive(header []byte) bool {
|
||||||
|
for _, m := range [][]byte{
|
||||||
|
{0x42, 0x5A, 0x68}, // bzip2
|
||||||
|
{0x1F, 0x8B, 0x08}, // gzip
|
||||||
|
{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, // xz
|
||||||
|
} {
|
||||||
|
if len(header) < len(m) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if bytes.Equal(m, header[:len(m)]) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r := tar.NewReader(bytes.NewBuffer(header))
|
||||||
|
_, err := r.Next()
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filesFromURLRef(ctx context.Context, c gwclient.Client, ref gwclient.Reference, inp *Input, filename string, names []string) ([]File, error) {
|
||||||
|
stat, err := ref.StatFile(ctx, gwclient.StatRequest{Path: filename})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{
|
||||||
|
Filename: filename,
|
||||||
|
Range: &gwclient.FileRange{
|
||||||
|
Length: 1024,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if isArchive(dt) {
|
||||||
|
bc := llb.Scratch().File(llb.Copy(inp.State, filename, "/", &llb.CopyInfo{
|
||||||
|
AttemptUnpack: true,
|
||||||
|
}))
|
||||||
|
inp.State = &bc
|
||||||
|
inp.URL = ""
|
||||||
|
def, err := bc.Marshal(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res, err := c.Solve(ctx, gwclient.SolveRequest{
|
||||||
|
Definition: def.ToPB(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := res.SingleRef()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return filesFromRef(ctx, ref, names)
|
||||||
|
}
|
||||||
|
|
||||||
|
inp.State = nil
|
||||||
|
name := inp.URL
|
||||||
|
inp.URL = ""
|
||||||
|
|
||||||
|
if len(dt) > stat.Size() {
|
||||||
|
if stat.Size() > 1024*512 {
|
||||||
|
return nil, errors.Errorf("non-archive definition URL bigger than maximum allowed size")
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err = ref.ReadFile(ctx, gwclient.ReadRequest{
|
||||||
|
Filename: filename,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []File{{Name: name, Data: dt}}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func filesFromRef(ctx context.Context, ref gwclient.Reference, names []string) ([]File, error) {
|
||||||
|
// TODO: auto-remove parent dir in needed
|
||||||
|
var files []File
|
||||||
|
|
||||||
|
isDefault := false
|
||||||
|
if len(names) == 0 {
|
||||||
|
isDefault = true
|
||||||
|
names = defaultFilenames()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
_, err := ref.StatFile(ctx, gwclient.StatRequest{Path: name})
|
||||||
|
if err != nil {
|
||||||
|
if isDefault {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{Filename: name})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
files = append(files, File{Name: name, Data: dt})
|
||||||
|
}
|
||||||
|
|
||||||
|
return files, nil
|
||||||
|
}
|
||||||
1473
build/build.go
1473
build/build.go
File diff suppressed because it is too large
Load Diff
@@ -1,60 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/csv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/moby/buildkit/client"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseCacheEntry(in []string) ([]client.CacheOptionsEntry, error) {
|
|
||||||
imports := make([]client.CacheOptionsEntry, 0, len(in))
|
|
||||||
for _, in := range in {
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(in))
|
|
||||||
fields, err := csvReader.Read()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if isRefOnlyFormat(fields) {
|
|
||||||
for _, field := range fields {
|
|
||||||
imports = append(imports, client.CacheOptionsEntry{
|
|
||||||
Type: "registry",
|
|
||||||
Attrs: map[string]string{"ref": field},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
im := client.CacheOptionsEntry{
|
|
||||||
Attrs: map[string]string{},
|
|
||||||
}
|
|
||||||
for _, field := range fields {
|
|
||||||
parts := strings.SplitN(field, "=", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return nil, errors.Errorf("invalid value %s", field)
|
|
||||||
}
|
|
||||||
key := strings.ToLower(parts[0])
|
|
||||||
value := parts[1]
|
|
||||||
switch key {
|
|
||||||
case "type":
|
|
||||||
im.Type = value
|
|
||||||
default:
|
|
||||||
im.Attrs[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if im.Type == "" {
|
|
||||||
return nil, errors.Errorf("type required form> %q", in)
|
|
||||||
}
|
|
||||||
imports = append(imports, im)
|
|
||||||
}
|
|
||||||
return imports, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func isRefOnlyFormat(in []string) bool {
|
|
||||||
for _, v := range in {
|
|
||||||
if strings.Contains(v, "=") {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
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")
|
||||||
|
}
|
||||||
352
build/driver.go
Normal file
352
build/driver.go
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"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 cachedGroup[T any] struct {
|
||||||
|
g flightcontrol.Group[T]
|
||||||
|
cache map[int]T
|
||||||
|
cacheMu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCachedGroup[T any]() cachedGroup[T] {
|
||||||
|
return cachedGroup[T]{
|
||||||
|
cache: map[int]T{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeResolver struct {
|
||||||
|
nodes []builder.Node
|
||||||
|
clients cachedGroup[*client.Client]
|
||||||
|
buildOpts cachedGroup[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,
|
||||||
|
clients: newCachedGroup[*client.Client](),
|
||||||
|
buildOpts: newCachedGroup[gateway.BuildOpts](),
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
nodeIdxs = append(nodeIdxs, 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.g.Do(ctx, fmt.Sprint(idx), func(ctx context.Context) (*client.Client, error) {
|
||||||
|
if r.nodes[idx].Driver == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
r.clients.cacheMu.Lock()
|
||||||
|
c, ok := r.clients.cache[idx]
|
||||||
|
r.clients.cacheMu.Unlock()
|
||||||
|
if ok {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
c, err := driver.Boot(ctx, baseCtx, r.nodes[idx].Driver, pw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r.clients.cacheMu.Lock()
|
||||||
|
r.clients.cache[idx] = c
|
||||||
|
r.clients.cacheMu.Unlock()
|
||||||
|
return c, nil
|
||||||
|
})
|
||||||
|
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.buildOpts.g.Do(ctx, fmt.Sprint(idx), func(ctx context.Context) (gateway.BuildOpts, error) {
|
||||||
|
r.buildOpts.cacheMu.Lock()
|
||||||
|
opt, ok := r.buildOpts.cache[idx]
|
||||||
|
r.buildOpts.cacheMu.Unlock()
|
||||||
|
if ok {
|
||||||
|
return opt, nil
|
||||||
|
}
|
||||||
|
_, 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)
|
||||||
|
if err != nil {
|
||||||
|
return gateway.BuildOpts{}, err
|
||||||
|
}
|
||||||
|
r.buildOpts.cacheMu.Lock()
|
||||||
|
r.buildOpts.cache[idx] = opt
|
||||||
|
r.buildOpts.cacheMu.Unlock()
|
||||||
|
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)
|
||||||
|
}
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/moby/buildkit/util/entitlements"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseEntitlements(in []string) ([]entitlements.Entitlement, error) {
|
|
||||||
out := make([]entitlements.Entitlement, 0, len(in))
|
|
||||||
for _, v := range in {
|
|
||||||
switch v {
|
|
||||||
case "security.insecure":
|
|
||||||
out = append(out, entitlements.EntitlementSecurityInsecure)
|
|
||||||
case "network.host":
|
|
||||||
out = append(out, entitlements.EntitlementNetworkHost)
|
|
||||||
default:
|
|
||||||
return nil, errors.Errorf("invalid entitlement: %v", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out, nil
|
|
||||||
}
|
|
||||||
131
build/git.go
Normal file
131
build/git.go
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"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) (map[string]string, func(key, dir string, so *client.SolveOpt), error) {
|
||||||
|
res := make(map[string]string)
|
||||||
|
if contextPath == "" {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
setGitLabels := false
|
||||||
|
if v, ok := os.LookupEnv("BUILDX_GIT_LABELS"); ok {
|
||||||
|
if v == "full" { // backward compatibility with old "full" mode
|
||||||
|
setGitLabels = true
|
||||||
|
} else if v, err := strconv.ParseBool(v); err == nil {
|
||||||
|
setGitLabels = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setGitInfo := true
|
||||||
|
if v, ok := os.LookupEnv("BUILDX_GIT_INFO"); ok {
|
||||||
|
if v, err := strconv.ParseBool(v); err == nil {
|
||||||
|
setGitInfo = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !setGitLabels && !setGitInfo {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out in which directory the git command needs to run in
|
||||||
|
var wd string
|
||||||
|
if filepath.IsAbs(contextPath) {
|
||||||
|
wd = contextPath
|
||||||
|
} else {
|
||||||
|
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, 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 nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !gitc.IsInsideWorkTree() {
|
||||||
|
if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() {
|
||||||
|
return res, nil, errors.New("failed to read current commit information with git rev-parse --is-inside-work-tree")
|
||||||
|
}
|
||||||
|
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, nil, errors.Wrap(err, "failed to get git commit")
|
||||||
|
} else if sha != "" {
|
||||||
|
checkDirty := false
|
||||||
|
if v, ok := os.LookupEnv("BUILDX_GIT_CHECK_DIRTY"); ok {
|
||||||
|
if v, err := strconv.ParseBool(v); err == nil {
|
||||||
|
checkDirty = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if checkDirty && gitc.IsDirty() {
|
||||||
|
sha += "-dirty"
|
||||||
|
}
|
||||||
|
if setGitLabels {
|
||||||
|
res["label:"+specs.AnnotationRevision] = sha
|
||||||
|
}
|
||||||
|
if setGitInfo {
|
||||||
|
res["vcs:revision"] = sha
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rurl, err := gitc.RemoteURL(); err == nil && rurl != "" {
|
||||||
|
if setGitLabels {
|
||||||
|
res["label:"+specs.AnnotationSource] = rurl
|
||||||
|
}
|
||||||
|
if setGitInfo {
|
||||||
|
res["vcs:source"] = rurl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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
|
||||||
|
}
|
||||||
209
build/git_test.go
Normal file
209
build/git_test.go
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTest(tb testing.TB) {
|
||||||
|
gitutil.Mktmp(tb)
|
||||||
|
|
||||||
|
c, err := gitutil.New()
|
||||||
|
require.NoError(tb, err)
|
||||||
|
gitutil.GitInit(c, tb)
|
||||||
|
|
||||||
|
df := []byte("FROM alpine:latest\n")
|
||||||
|
assert.NoError(tb, os.WriteFile("Dockerfile", df, 0644))
|
||||||
|
|
||||||
|
gitutil.GitAdd(c, tb, "Dockerfile")
|
||||||
|
gitutil.GitCommit(c, tb, "initial commit")
|
||||||
|
gitutil.GitSetRemote(c, tb, "origin", "git@github.com:docker/buildx.git")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitAttributesNotGitRepo(t *testing.T) {
|
||||||
|
_, _, err := getGitAttributes(context.Background(), t.TempDir(), "Dockerfile")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitAttributesBadGitRepo(t *testing.T) {
|
||||||
|
tmp := t.TempDir()
|
||||||
|
require.NoError(t, os.MkdirAll(path.Join(tmp, ".git"), 0755))
|
||||||
|
|
||||||
|
_, _, err := getGitAttributes(context.Background(), tmp, "Dockerfile")
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitAttributesNoContext(t *testing.T) {
|
||||||
|
setupTest(t)
|
||||||
|
|
||||||
|
gitattrs, _, err := getGitAttributes(context.Background(), "", "Dockerfile")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, gitattrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitAttributes(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
envGitLabels string
|
||||||
|
envGitInfo string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "default",
|
||||||
|
envGitLabels: "",
|
||||||
|
envGitInfo: "",
|
||||||
|
expected: []string{
|
||||||
|
"vcs:revision",
|
||||||
|
"vcs:source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none",
|
||||||
|
envGitLabels: "false",
|
||||||
|
envGitInfo: "false",
|
||||||
|
expected: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gitinfo",
|
||||||
|
envGitLabels: "false",
|
||||||
|
envGitInfo: "true",
|
||||||
|
expected: []string{
|
||||||
|
"vcs:revision",
|
||||||
|
"vcs:source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gitlabels",
|
||||||
|
envGitLabels: "true",
|
||||||
|
envGitInfo: "false",
|
||||||
|
expected: []string{
|
||||||
|
"label:" + DockerfileLabel,
|
||||||
|
"label:" + specs.AnnotationRevision,
|
||||||
|
"label:" + specs.AnnotationSource,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "both",
|
||||||
|
envGitLabels: "true",
|
||||||
|
envGitInfo: "",
|
||||||
|
expected: []string{
|
||||||
|
"label:" + DockerfileLabel,
|
||||||
|
"label:" + specs.AnnotationRevision,
|
||||||
|
"label:" + specs.AnnotationSource,
|
||||||
|
"vcs:revision",
|
||||||
|
"vcs:source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range cases {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
setupTest(t)
|
||||||
|
if tt.envGitLabels != "" {
|
||||||
|
t.Setenv("BUILDX_GIT_LABELS", tt.envGitLabels)
|
||||||
|
}
|
||||||
|
if tt.envGitInfo != "" {
|
||||||
|
t.Setenv("BUILDX_GIT_INFO", tt.envGitInfo)
|
||||||
|
}
|
||||||
|
gitattrs, _, err := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||||
|
require.NoError(t, err)
|
||||||
|
for _, e := range tt.expected {
|
||||||
|
assert.Contains(t, gitattrs, e)
|
||||||
|
assert.NotEmpty(t, gitattrs[e])
|
||||||
|
if e == "label:"+DockerfileLabel {
|
||||||
|
assert.Equal(t, "Dockerfile", gitattrs[e])
|
||||||
|
} else if e == "label:"+specs.AnnotationSource || e == "vcs:source" {
|
||||||
|
assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs[e])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetGitAttributesDirty(t *testing.T) {
|
||||||
|
setupTest(t)
|
||||||
|
t.Setenv("BUILDX_GIT_CHECK_DIRTY", "true")
|
||||||
|
|
||||||
|
// make a change to test dirty flag
|
||||||
|
df := []byte("FROM alpine:edge\n")
|
||||||
|
require.NoError(t, os.Mkdir("dir", 0755))
|
||||||
|
require.NoError(t, os.WriteFile(filepath.Join("dir", "Dockerfile"), df, 0644))
|
||||||
|
|
||||||
|
t.Setenv("BUILDX_GIT_LABELS", "true")
|
||||||
|
gitattrs, _, _ := getGitAttributes(context.Background(), ".", "Dockerfile")
|
||||||
|
assert.Equal(t, 5, len(gitattrs))
|
||||||
|
|
||||||
|
assert.Contains(t, gitattrs, "label:"+DockerfileLabel)
|
||||||
|
assert.Equal(t, "Dockerfile", gitattrs["label:"+DockerfileLabel])
|
||||||
|
assert.Contains(t, gitattrs, "label:"+specs.AnnotationSource)
|
||||||
|
assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs["label:"+specs.AnnotationSource])
|
||||||
|
assert.Contains(t, gitattrs, "label:"+specs.AnnotationRevision)
|
||||||
|
assert.True(t, strings.HasSuffix(gitattrs["label:"+specs.AnnotationRevision], "-dirty"))
|
||||||
|
|
||||||
|
assert.Contains(t, gitattrs, "vcs:source")
|
||||||
|
assert.Equal(t, "git@github.com:docker/buildx.git", gitattrs["vcs:source"])
|
||||||
|
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"])
|
||||||
|
}
|
||||||
138
build/invoke.go
Normal file
138
build/invoke.go
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "crypto/sha256" // ensure digests can be computed
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
|
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Container struct {
|
||||||
|
cancelOnce sync.Once
|
||||||
|
containerCancel func()
|
||||||
|
isUnavailable atomic.Bool
|
||||||
|
initStarted atomic.Bool
|
||||||
|
container gateway.Container
|
||||||
|
releaseCh chan struct{}
|
||||||
|
resultCtx *ResultHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewContainer(ctx context.Context, resultCtx *ResultHandle, cfg *controllerapi.InvokeConfig) (*Container, error) {
|
||||||
|
mainCtx := ctx
|
||||||
|
|
||||||
|
ctrCh := make(chan *Container)
|
||||||
|
errCh := make(chan error)
|
||||||
|
go func() {
|
||||||
|
err := resultCtx.build(func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
go func() {
|
||||||
|
<-mainCtx.Done()
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
containerCfg, err := resultCtx.getContainerConfig(ctx, c, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
containerCtx, containerCancel := context.WithCancel(ctx)
|
||||||
|
defer containerCancel()
|
||||||
|
bkContainer, err := c.NewContainer(containerCtx, containerCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
releaseCh := make(chan struct{})
|
||||||
|
container := &Container{
|
||||||
|
containerCancel: containerCancel,
|
||||||
|
container: bkContainer,
|
||||||
|
releaseCh: releaseCh,
|
||||||
|
resultCtx: resultCtx,
|
||||||
|
}
|
||||||
|
doneCh := make(chan struct{})
|
||||||
|
defer close(doneCh)
|
||||||
|
resultCtx.registerCleanup(func() {
|
||||||
|
container.Cancel()
|
||||||
|
<-doneCh
|
||||||
|
})
|
||||||
|
ctrCh <- container
|
||||||
|
<-container.releaseCh
|
||||||
|
|
||||||
|
return nil, bkContainer.Release(ctx)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
errCh <- err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case ctr := <-ctrCh:
|
||||||
|
return ctr, nil
|
||||||
|
case err := <-errCh:
|
||||||
|
return nil, err
|
||||||
|
case <-mainCtx.Done():
|
||||||
|
return nil, mainCtx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) Cancel() {
|
||||||
|
c.markUnavailable()
|
||||||
|
c.cancelOnce.Do(func() {
|
||||||
|
if c.containerCancel != nil {
|
||||||
|
c.containerCancel()
|
||||||
|
}
|
||||||
|
close(c.releaseCh)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) IsUnavailable() bool {
|
||||||
|
return c.isUnavailable.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) markUnavailable() {
|
||||||
|
c.isUnavailable.Store(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) Exec(ctx context.Context, cfg *controllerapi.InvokeConfig, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
|
||||||
|
if isInit := c.initStarted.CompareAndSwap(false, true); isInit {
|
||||||
|
defer func() {
|
||||||
|
// container can't be used after init exits
|
||||||
|
c.markUnavailable()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
err := exec(ctx, c.resultCtx, cfg, c.container, stdin, stdout, stderr)
|
||||||
|
if err != nil {
|
||||||
|
// Container becomes unavailable if one of the processes fails in it.
|
||||||
|
c.markUnavailable()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func exec(ctx context.Context, resultCtx *ResultHandle, cfg *controllerapi.InvokeConfig, ctr gateway.Container, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
|
||||||
|
processCfg, err := resultCtx.getProcessConfig(cfg, stdin, stdout, stderr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
proc, err := ctr.Start(ctx, processCfg)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Errorf("failed to start container: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
doneCh := make(chan struct{})
|
||||||
|
defer close(doneCh)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
if err := proc.Signal(ctx, syscall.SIGKILL); err != nil {
|
||||||
|
logrus.Warnf("failed to kill process: %v", err)
|
||||||
|
}
|
||||||
|
case <-doneCh:
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return proc.Wait()
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
646
build/opt.go
Normal file
646
build/opt.go
Normal file
@@ -0,0 +1,646 @@
|
|||||||
|
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() && opt.PrintFunc == nil {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark info request as internal
|
||||||
|
if opt.PrintFunc != nil {
|
||||||
|
so.Internal = true
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
115
build/output.go
115
build/output.go
@@ -1,115 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/csv"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/containerd/console"
|
|
||||||
"github.com/moby/buildkit/client"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseOutputs(inp []string) ([]client.ExportEntry, error) {
|
|
||||||
var outs []client.ExportEntry
|
|
||||||
if len(inp) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
for _, s := range inp {
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(s))
|
|
||||||
fields, err := csvReader.Read()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
out := client.ExportEntry{
|
|
||||||
Attrs: map[string]string{},
|
|
||||||
}
|
|
||||||
if len(fields) == 1 && fields[0] == s && !strings.HasPrefix(s, "type=") {
|
|
||||||
if s != "-" {
|
|
||||||
outs = append(outs, client.ExportEntry{
|
|
||||||
Type: client.ExporterLocal,
|
|
||||||
OutputDir: s,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
out = client.ExportEntry{
|
|
||||||
Type: client.ExporterTar,
|
|
||||||
Attrs: map[string]string{
|
|
||||||
"dest": s,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.Type == "" {
|
|
||||||
for _, field := range fields {
|
|
||||||
parts := strings.SplitN(field, "=", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return nil, errors.Errorf("invalid value %s", field)
|
|
||||||
}
|
|
||||||
key := strings.TrimSpace(strings.ToLower(parts[0]))
|
|
||||||
value := parts[1]
|
|
||||||
switch key {
|
|
||||||
case "type":
|
|
||||||
out.Type = value
|
|
||||||
default:
|
|
||||||
out.Attrs[key] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if out.Type == "" {
|
|
||||||
return nil, errors.Errorf("type is required for output")
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle client side
|
|
||||||
switch out.Type {
|
|
||||||
case client.ExporterLocal:
|
|
||||||
dest, ok := out.Attrs["dest"]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf("dest is required for local output")
|
|
||||||
}
|
|
||||||
out.OutputDir = dest
|
|
||||||
delete(out.Attrs, "dest")
|
|
||||||
case client.ExporterOCI, client.ExporterDocker, client.ExporterTar:
|
|
||||||
dest, ok := out.Attrs["dest"]
|
|
||||||
if !ok {
|
|
||||||
if out.Type != client.ExporterDocker {
|
|
||||||
dest = "-"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dest == "-" {
|
|
||||||
if _, err := console.ConsoleFromFile(os.Stdout); err == nil {
|
|
||||||
return nil, errors.Errorf("output file is required for %s exporter. refusing to write to console", out.Type)
|
|
||||||
}
|
|
||||||
out.Output = wrapWriteCloser(os.Stdout)
|
|
||||||
} else if dest != "" {
|
|
||||||
fi, err := os.Stat(dest)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return nil, errors.Wrapf(err, "invalid destination file: %s", dest)
|
|
||||||
}
|
|
||||||
if err == nil && fi.IsDir() {
|
|
||||||
return nil, errors.Errorf("destination file %s is a directory", dest)
|
|
||||||
}
|
|
||||||
f, err := os.Create(dest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Errorf("failed to open %s", err)
|
|
||||||
}
|
|
||||||
out.Output = wrapWriteCloser(f)
|
|
||||||
}
|
|
||||||
delete(out.Attrs, "dest")
|
|
||||||
case "registry":
|
|
||||||
out.Type = client.ExporterImage
|
|
||||||
out.Attrs["push"] = "true"
|
|
||||||
}
|
|
||||||
|
|
||||||
outs = append(outs, out)
|
|
||||||
}
|
|
||||||
return outs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func wrapWriteCloser(wc io.WriteCloser) func(map[string]string) (io.WriteCloser, error) {
|
|
||||||
return func(map[string]string) (io.WriteCloser, error) {
|
|
||||||
return wc, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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
|
||||||
|
}
|
||||||
495
build/result.go
Normal file
495
build/result.go
Normal file
@@ -0,0 +1,495 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "crypto/sha256" // ensure digests can be computed
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/exporter/containerimage/exptypes"
|
||||||
|
gateway "github.com/moby/buildkit/frontend/gateway/client"
|
||||||
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
|
"github.com/moby/buildkit/solver/pb"
|
||||||
|
"github.com/moby/buildkit/solver/result"
|
||||||
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewResultHandle makes a call to client.Build, additionally returning a
|
||||||
|
// opaque ResultHandle alongside the standard response and error.
|
||||||
|
//
|
||||||
|
// This ResultHandle can be used to execute additional build steps in the same
|
||||||
|
// context as the build occurred, which can allow easy debugging of build
|
||||||
|
// failures and successes.
|
||||||
|
//
|
||||||
|
// If the returned ResultHandle is not nil, the caller must call Done() on it.
|
||||||
|
func NewResultHandle(ctx context.Context, cc *client.Client, opt client.SolveOpt, product string, buildFunc gateway.BuildFunc, ch chan *client.SolveStatus) (*ResultHandle, *client.SolveResponse, error) {
|
||||||
|
// Create a new context to wrap the original, and cancel it when the
|
||||||
|
// caller-provided context is cancelled.
|
||||||
|
//
|
||||||
|
// We derive the context from the background context so that we can forbid
|
||||||
|
// cancellation of the build request after <-done is closed (which we do
|
||||||
|
// before returning the ResultHandle).
|
||||||
|
baseCtx := ctx
|
||||||
|
ctx, cancel := context.WithCancelCause(context.Background())
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-baseCtx.Done():
|
||||||
|
cancel(baseCtx.Err())
|
||||||
|
case <-done:
|
||||||
|
// Once done is closed, we've recorded a ResultHandle, so we
|
||||||
|
// shouldn't allow cancelling the underlying build request anymore.
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Create a new channel to forward status messages to the original.
|
||||||
|
//
|
||||||
|
// We do this so that we can discard status messages after the main portion
|
||||||
|
// of the build is complete. This is necessary for the solve error case,
|
||||||
|
// where the original gateway is kept open until the ResultHandle is
|
||||||
|
// closed - we don't want progress messages from operations in that
|
||||||
|
// ResultHandle to display after this function exits.
|
||||||
|
//
|
||||||
|
// Additionally, callers should wait for the progress channel to be closed.
|
||||||
|
// If we keep the session open and never close the progress channel, the
|
||||||
|
// caller will likely hang.
|
||||||
|
baseCh := ch
|
||||||
|
ch = make(chan *client.SolveStatus)
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
s, ok := <-ch
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-baseCh:
|
||||||
|
// base channel is closed, discard status messages
|
||||||
|
default:
|
||||||
|
baseCh <- s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
defer close(baseCh)
|
||||||
|
|
||||||
|
var resp *client.SolveResponse
|
||||||
|
var respErr error
|
||||||
|
var respHandle *ResultHandle
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer cancel(context.Canceled) // ensure no dangling processes
|
||||||
|
|
||||||
|
var res *gateway.Result
|
||||||
|
var err error
|
||||||
|
resp, err = cc.Build(ctx, opt, product, func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
|
||||||
|
var err error
|
||||||
|
res, err = buildFunc(ctx, c)
|
||||||
|
|
||||||
|
if res != nil && err == nil {
|
||||||
|
// Force evaluation of the build result (otherwise, we likely
|
||||||
|
// won't get a solve error)
|
||||||
|
def, err2 := getDefinition(ctx, res)
|
||||||
|
if err2 != nil {
|
||||||
|
return nil, err2
|
||||||
|
}
|
||||||
|
res, err = evalDefinition(ctx, c, def)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// Scenario 1: we failed to evaluate a node somewhere in the
|
||||||
|
// build graph.
|
||||||
|
//
|
||||||
|
// In this case, we construct a ResultHandle from this
|
||||||
|
// original Build session, and return it alongside the original
|
||||||
|
// build error. We then need to keep the gateway session open
|
||||||
|
// until the caller explicitly closes the ResultHandle.
|
||||||
|
|
||||||
|
var se *errdefs.SolveError
|
||||||
|
if errors.As(err, &se) {
|
||||||
|
respHandle = &ResultHandle{
|
||||||
|
done: make(chan struct{}),
|
||||||
|
solveErr: se,
|
||||||
|
gwClient: c,
|
||||||
|
gwCtx: ctx,
|
||||||
|
}
|
||||||
|
respErr = err // return original error to preserve stacktrace
|
||||||
|
close(done)
|
||||||
|
|
||||||
|
// Block until the caller closes the ResultHandle.
|
||||||
|
select {
|
||||||
|
case <-respHandle.done:
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, err
|
||||||
|
}, ch)
|
||||||
|
if respHandle != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// Something unexpected failed during the build, we didn't succeed,
|
||||||
|
// but we also didn't make it far enough to create a ResultHandle.
|
||||||
|
respErr = err
|
||||||
|
close(done)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scenario 2: we successfully built the image with no errors.
|
||||||
|
//
|
||||||
|
// In this case, the original gateway session has now been closed
|
||||||
|
// since the Build has been completed. So, we need to create a new
|
||||||
|
// gateway session to populate the ResultHandle. To do this, we
|
||||||
|
// need to re-evaluate the target result, in this new session. This
|
||||||
|
// should be instantaneous since the result should be cached.
|
||||||
|
|
||||||
|
def, err := getDefinition(ctx, res)
|
||||||
|
if err != nil {
|
||||||
|
respErr = err
|
||||||
|
close(done)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: ideally this second connection should be lazily opened
|
||||||
|
opt := opt
|
||||||
|
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 {
|
||||||
|
// This should probably not happen, since we've previously
|
||||||
|
// successfully evaluated the same result with no issues.
|
||||||
|
return nil, errors.Wrap(err, "inconsistent solve result")
|
||||||
|
}
|
||||||
|
respHandle = &ResultHandle{
|
||||||
|
done: make(chan struct{}),
|
||||||
|
res: res,
|
||||||
|
gwClient: c,
|
||||||
|
gwCtx: ctx,
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
|
||||||
|
// Block until the caller closes the ResultHandle.
|
||||||
|
select {
|
||||||
|
case <-respHandle.done:
|
||||||
|
case <-ctx.Done():
|
||||||
|
}
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}, nil)
|
||||||
|
if respHandle != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
close(done)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Block until the other thread signals that it's completed the build.
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-baseCtx.Done():
|
||||||
|
if respErr == nil {
|
||||||
|
respErr = baseCtx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return respHandle, resp, respErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDefinition converts a gateway result into a collection of definitions for
|
||||||
|
// each ref in the result.
|
||||||
|
func getDefinition(ctx context.Context, res *gateway.Result) (*result.Result[*pb.Definition], error) {
|
||||||
|
return result.ConvertResult(res, func(ref gateway.Reference) (*pb.Definition, error) {
|
||||||
|
st, err := ref.ToState()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
def, err := st.Marshal(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return def.ToPB(), nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// evalDefinition performs the reverse of getDefinition, converting a
|
||||||
|
// collection of definitions into a gateway result.
|
||||||
|
func evalDefinition(ctx context.Context, c gateway.Client, defs *result.Result[*pb.Definition]) (*gateway.Result, error) {
|
||||||
|
// force evaluation of all targets in parallel
|
||||||
|
results := make(map[*pb.Definition]*gateway.Result)
|
||||||
|
resultsMu := sync.Mutex{}
|
||||||
|
eg, egCtx := errgroup.WithContext(ctx)
|
||||||
|
defs.EachRef(func(def *pb.Definition) error {
|
||||||
|
eg.Go(func() error {
|
||||||
|
res, err := c.Solve(egCtx, gateway.SolveRequest{
|
||||||
|
Evaluate: true,
|
||||||
|
Definition: def,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resultsMu.Lock()
|
||||||
|
results[def] = res
|
||||||
|
resultsMu.Unlock()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res, _ := result.ConvertResult(defs, func(def *pb.Definition) (gateway.Reference, error) {
|
||||||
|
if res, ok := results[def]; ok {
|
||||||
|
return res.Ref, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResultHandle is a build result with the client that built it.
|
||||||
|
type ResultHandle struct {
|
||||||
|
res *gateway.Result
|
||||||
|
solveErr *errdefs.SolveError
|
||||||
|
|
||||||
|
done chan struct{}
|
||||||
|
doneOnce sync.Once
|
||||||
|
|
||||||
|
gwClient gateway.Client
|
||||||
|
gwCtx context.Context
|
||||||
|
|
||||||
|
cleanups []func()
|
||||||
|
cleanupsMu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResultHandle) Done() {
|
||||||
|
r.doneOnce.Do(func() {
|
||||||
|
r.cleanupsMu.Lock()
|
||||||
|
cleanups := r.cleanups
|
||||||
|
r.cleanups = nil
|
||||||
|
r.cleanupsMu.Unlock()
|
||||||
|
for _, f := range cleanups {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
close(r.done)
|
||||||
|
<-r.gwCtx.Done()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResultHandle) registerCleanup(f func()) {
|
||||||
|
r.cleanupsMu.Lock()
|
||||||
|
r.cleanups = append(r.cleanups, f)
|
||||||
|
r.cleanupsMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResultHandle) build(buildFunc gateway.BuildFunc) (err error) {
|
||||||
|
_, err = buildFunc(r.gwCtx, r.gwClient)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResultHandle) getContainerConfig(ctx context.Context, c gateway.Client, cfg *controllerapi.InvokeConfig) (containerCfg gateway.NewContainerRequest, _ error) {
|
||||||
|
if r.res != nil && r.solveErr == nil {
|
||||||
|
logrus.Debugf("creating container from successful build")
|
||||||
|
ccfg, err := containerConfigFromResult(ctx, r.res, c, *cfg)
|
||||||
|
if err != nil {
|
||||||
|
return containerCfg, err
|
||||||
|
}
|
||||||
|
containerCfg = *ccfg
|
||||||
|
} else {
|
||||||
|
logrus.Debugf("creating container from failed build %+v", cfg)
|
||||||
|
ccfg, err := containerConfigFromError(r.solveErr, *cfg)
|
||||||
|
if err != nil {
|
||||||
|
return containerCfg, errors.Wrapf(err, "no result nor error is available")
|
||||||
|
}
|
||||||
|
containerCfg = *ccfg
|
||||||
|
}
|
||||||
|
return containerCfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ResultHandle) getProcessConfig(cfg *controllerapi.InvokeConfig, stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) (_ gateway.StartRequest, err error) {
|
||||||
|
processCfg := newStartRequest(stdin, stdout, stderr)
|
||||||
|
if r.res != nil && r.solveErr == nil {
|
||||||
|
logrus.Debugf("creating container from successful build")
|
||||||
|
if err := populateProcessConfigFromResult(&processCfg, r.res, *cfg); err != nil {
|
||||||
|
return processCfg, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logrus.Debugf("creating container from failed build %+v", cfg)
|
||||||
|
if err := populateProcessConfigFromError(&processCfg, r.solveErr, *cfg); err != nil {
|
||||||
|
return processCfg, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return processCfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerConfigFromResult(ctx context.Context, res *gateway.Result, c gateway.Client, cfg controllerapi.InvokeConfig) (*gateway.NewContainerRequest, error) {
|
||||||
|
if cfg.Initial {
|
||||||
|
return nil, errors.Errorf("starting from the container from the initial state of the step is supported only on the failed steps")
|
||||||
|
}
|
||||||
|
|
||||||
|
ps, err := exptypes.ParsePlatforms(res.Metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ref, ok := res.FindRef(ps.Platforms[0].ID)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("no reference found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &gateway.NewContainerRequest{
|
||||||
|
Mounts: []gateway.Mount{
|
||||||
|
{
|
||||||
|
Dest: "/",
|
||||||
|
MountType: pb.MountType_BIND,
|
||||||
|
Ref: ref,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateProcessConfigFromResult(req *gateway.StartRequest, res *gateway.Result, cfg controllerapi.InvokeConfig) error {
|
||||||
|
imgData := res.Metadata[exptypes.ExporterImageConfigKey]
|
||||||
|
var img *specs.Image
|
||||||
|
if len(imgData) > 0 {
|
||||||
|
img = &specs.Image{}
|
||||||
|
if err := json.Unmarshal(imgData, img); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
user := ""
|
||||||
|
if !cfg.NoUser {
|
||||||
|
user = cfg.User
|
||||||
|
} else if img != nil {
|
||||||
|
user = img.Config.User
|
||||||
|
}
|
||||||
|
|
||||||
|
cwd := ""
|
||||||
|
if !cfg.NoCwd {
|
||||||
|
cwd = cfg.Cwd
|
||||||
|
} else if img != nil {
|
||||||
|
cwd = img.Config.WorkingDir
|
||||||
|
}
|
||||||
|
|
||||||
|
env := []string{}
|
||||||
|
if img != nil {
|
||||||
|
env = append(env, img.Config.Env...)
|
||||||
|
}
|
||||||
|
env = append(env, cfg.Env...)
|
||||||
|
|
||||||
|
args := []string{}
|
||||||
|
if cfg.Entrypoint != nil {
|
||||||
|
args = append(args, cfg.Entrypoint...)
|
||||||
|
} else if img != nil {
|
||||||
|
args = append(args, img.Config.Entrypoint...)
|
||||||
|
}
|
||||||
|
if !cfg.NoCmd {
|
||||||
|
args = append(args, cfg.Cmd...)
|
||||||
|
} else if img != nil {
|
||||||
|
args = append(args, img.Config.Cmd...)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Args = args
|
||||||
|
req.Env = env
|
||||||
|
req.User = user
|
||||||
|
req.Cwd = cwd
|
||||||
|
req.Tty = cfg.Tty
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func containerConfigFromError(solveErr *errdefs.SolveError, cfg controllerapi.InvokeConfig) (*gateway.NewContainerRequest, error) {
|
||||||
|
exec, err := execOpFromError(solveErr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var mounts []gateway.Mount
|
||||||
|
for i, mnt := range exec.Mounts {
|
||||||
|
rid := solveErr.Solve.MountIDs[i]
|
||||||
|
if cfg.Initial {
|
||||||
|
rid = solveErr.Solve.InputIDs[i]
|
||||||
|
}
|
||||||
|
mounts = append(mounts, gateway.Mount{
|
||||||
|
Selector: mnt.Selector,
|
||||||
|
Dest: mnt.Dest,
|
||||||
|
ResultID: rid,
|
||||||
|
Readonly: mnt.Readonly,
|
||||||
|
MountType: mnt.MountType,
|
||||||
|
CacheOpt: mnt.CacheOpt,
|
||||||
|
SecretOpt: mnt.SecretOpt,
|
||||||
|
SSHOpt: mnt.SSHOpt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &gateway.NewContainerRequest{
|
||||||
|
Mounts: mounts,
|
||||||
|
NetMode: exec.Network,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func populateProcessConfigFromError(req *gateway.StartRequest, solveErr *errdefs.SolveError, cfg controllerapi.InvokeConfig) error {
|
||||||
|
exec, err := execOpFromError(solveErr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
meta := exec.Meta
|
||||||
|
user := ""
|
||||||
|
if !cfg.NoUser {
|
||||||
|
user = cfg.User
|
||||||
|
} else {
|
||||||
|
user = meta.User
|
||||||
|
}
|
||||||
|
|
||||||
|
cwd := ""
|
||||||
|
if !cfg.NoCwd {
|
||||||
|
cwd = cfg.Cwd
|
||||||
|
} else {
|
||||||
|
cwd = meta.Cwd
|
||||||
|
}
|
||||||
|
|
||||||
|
env := append(meta.Env, cfg.Env...)
|
||||||
|
|
||||||
|
args := []string{}
|
||||||
|
if cfg.Entrypoint != nil {
|
||||||
|
args = append(args, cfg.Entrypoint...)
|
||||||
|
}
|
||||||
|
if cfg.Cmd != nil {
|
||||||
|
args = append(args, cfg.Cmd...)
|
||||||
|
}
|
||||||
|
if len(args) == 0 {
|
||||||
|
args = meta.Args
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Args = args
|
||||||
|
req.Env = env
|
||||||
|
req.User = user
|
||||||
|
req.Cwd = cwd
|
||||||
|
req.Tty = cfg.Tty
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func execOpFromError(solveErr *errdefs.SolveError) (*pb.ExecOp, error) {
|
||||||
|
if solveErr == nil {
|
||||||
|
return nil, errors.Errorf("no error is available")
|
||||||
|
}
|
||||||
|
switch op := solveErr.Solve.Op.GetOp().(type) {
|
||||||
|
case *pb.Op_Exec:
|
||||||
|
return op.Exec, nil
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("invoke: unsupported error type")
|
||||||
|
}
|
||||||
|
// TODO: support other ops
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStartRequest(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) gateway.StartRequest {
|
||||||
|
return gateway.StartRequest{
|
||||||
|
Stdin: stdin,
|
||||||
|
Stdout: stdout,
|
||||||
|
Stderr: stderr,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/csv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/moby/buildkit/session"
|
|
||||||
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseSecretSpecs(sl []string) (session.Attachable, error) {
|
|
||||||
fs := make([]secretsprovider.FileSource, 0, len(sl))
|
|
||||||
for _, v := range sl {
|
|
||||||
s, err := parseSecret(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fs = append(fs, *s)
|
|
||||||
}
|
|
||||||
store, err := secretsprovider.NewFileStore(fs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return secretsprovider.NewSecretProvider(store), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSecret(value string) (*secretsprovider.FileSource, error) {
|
|
||||||
csvReader := csv.NewReader(strings.NewReader(value))
|
|
||||||
fields, err := csvReader.Read()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to parse csv secret")
|
|
||||||
}
|
|
||||||
|
|
||||||
fs := secretsprovider.FileSource{}
|
|
||||||
|
|
||||||
for _, field := range fields {
|
|
||||||
parts := strings.SplitN(field, "=", 2)
|
|
||||||
key := strings.ToLower(parts[0])
|
|
||||||
|
|
||||||
if len(parts) != 2 {
|
|
||||||
return nil, errors.Errorf("invalid field '%s' must be a key=value pair", field)
|
|
||||||
}
|
|
||||||
|
|
||||||
value := parts[1]
|
|
||||||
switch key {
|
|
||||||
case "type":
|
|
||||||
if value != "file" {
|
|
||||||
return nil, errors.Errorf("unsupported secret type %q", value)
|
|
||||||
}
|
|
||||||
case "id":
|
|
||||||
fs.ID = value
|
|
||||||
case "source", "src":
|
|
||||||
fs.FilePath = value
|
|
||||||
default:
|
|
||||||
return nil, errors.Errorf("unexpected key '%s' in '%s'", key, field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &fs, nil
|
|
||||||
}
|
|
||||||
31
build/ssh.go
31
build/ssh.go
@@ -1,31 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/moby/buildkit/session"
|
|
||||||
"github.com/moby/buildkit/session/sshforward/sshprovider"
|
|
||||||
)
|
|
||||||
|
|
||||||
func ParseSSHSpecs(sl []string) (session.Attachable, error) {
|
|
||||||
configs := make([]sshprovider.AgentConfig, 0, len(sl))
|
|
||||||
for _, v := range sl {
|
|
||||||
c, err := parseSSH(v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
configs = append(configs, *c)
|
|
||||||
}
|
|
||||||
return sshprovider.NewSSHAgentProvider(configs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSSH(value string) (*sshprovider.AgentConfig, error) {
|
|
||||||
parts := strings.SplitN(value, "=", 2)
|
|
||||||
cfg := sshprovider.AgentConfig{
|
|
||||||
ID: parts[0],
|
|
||||||
}
|
|
||||||
if len(parts) > 1 {
|
|
||||||
cfg.Paths = strings.Split(parts[1], ",")
|
|
||||||
}
|
|
||||||
return &cfg, nil
|
|
||||||
}
|
|
||||||
71
build/url.go
Normal file
71
build/url.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/driver"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/client/llb"
|
||||||
|
gwclient "github.com/moby/buildkit/frontend/gateway/client"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
var out string
|
||||||
|
ch, done := progress.NewChannel(pw)
|
||||||
|
defer func() { <-done }()
|
||||||
|
_, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := c.Solve(ctx, gwclient.SolveRequest{
|
||||||
|
Definition: def.ToPB(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ref, err := res.SingleRef()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
stat, err := ref.StatFile(ctx, gwclient.StatRequest{
|
||||||
|
Path: "Dockerfile",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if stat.Size() > 512*1024 {
|
||||||
|
return nil, errors.Errorf("Dockerfile %s bigger than allowed max size", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
dt, err := ref.ReadFile(ctx, gwclient.ReadRequest{
|
||||||
|
Filename: "Dockerfile",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
dir, err := os.MkdirTemp("", "buildx")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "Dockerfile"), dt, 0600); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
out = dir
|
||||||
|
return nil, nil
|
||||||
|
}, ch)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
@@ -3,19 +3,37 @@ package build
|
|||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"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/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// archiveHeaderSize is the number of bytes in an archive header
|
const (
|
||||||
const archiveHeaderSize = 512
|
// archiveHeaderSize is the number of bytes in an archive header
|
||||||
|
archiveHeaderSize = 512
|
||||||
|
// mobyHostGatewayName defines a special string which users can append to
|
||||||
|
// --add-host to add an extra entry in /etc/hosts that maps
|
||||||
|
// host.docker.internal to the host IP
|
||||||
|
mobyHostGatewayName = "host-gateway"
|
||||||
|
)
|
||||||
|
|
||||||
func isLocalDir(c string) bool {
|
func IsRemoteURL(c string) bool {
|
||||||
st, err := os.Stat(c)
|
if urlutil.IsURL(c) {
|
||||||
return err == nil && st.IsDir()
|
return true
|
||||||
|
}
|
||||||
|
if _, err := gitutil.ParseGitRef(c); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func isArchive(header []byte) bool {
|
func isArchive(header []byte) bool {
|
||||||
@@ -38,18 +56,69 @@ func isArchive(header []byte) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// toBuildkitExtraHosts converts hosts from docker key:value format to buildkit's csv format
|
// toBuildkitExtraHosts converts hosts from docker key:value format to buildkit's csv format
|
||||||
func toBuildkitExtraHosts(inp []string) (string, error) {
|
func toBuildkitExtraHosts(ctx context.Context, inp []string, nodeDriver *driver.DriverHandle) (string, error) {
|
||||||
if len(inp) == 0 {
|
if len(inp) == 0 {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
hosts := make([]string, 0, len(inp))
|
hosts := make([]string, 0, len(inp))
|
||||||
for _, h := range inp {
|
for _, h := range inp {
|
||||||
parts := strings.Split(h, ":")
|
host, ip, ok := strings.Cut(h, "=")
|
||||||
|
if !ok {
|
||||||
if len(parts) != 2 || parts[0] == "" || net.ParseIP(parts[1]) == nil {
|
host, ip, ok = strings.Cut(h, ":")
|
||||||
|
}
|
||||||
|
if !ok || host == "" || ip == "" {
|
||||||
return "", errors.Errorf("invalid host %s", h)
|
return "", errors.Errorf("invalid host %s", h)
|
||||||
}
|
}
|
||||||
hosts = append(hosts, parts[0]+"="+parts[1])
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hosts = append(hosts, host+"="+ip)
|
||||||
}
|
}
|
||||||
return strings.Join(hosts, ","), nil
|
return strings.Join(hosts, ","), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// toBuildkitUlimits converts ulimits from docker type=soft:hard format to buildkit's csv format
|
||||||
|
func toBuildkitUlimits(inp *opts.UlimitOpt) (string, error) {
|
||||||
|
if inp == nil || len(inp.GetList()) == 0 {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
ulimits := make([]string, 0, len(inp.GetList()))
|
||||||
|
for _, ulimit := range inp.GetList() {
|
||||||
|
ulimits = append(ulimits, ulimit.String())
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
677
builder/builder.go
Normal file
677
builder/builder.go
Normal file
@@ -0,0 +1,677 @@
|
|||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Builder represents an active builder object
|
||||||
|
type Builder struct {
|
||||||
|
*store.NodeGroup
|
||||||
|
driverFactory driverFactory
|
||||||
|
nodes []Node
|
||||||
|
opts builderOpts
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type builderOpts struct {
|
||||||
|
dockerCli command.Cli
|
||||||
|
name string
|
||||||
|
txn *store.Txn
|
||||||
|
contextPathHash string
|
||||||
|
validate bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option provides a variadic option for configuring the builder.
|
||||||
|
type Option func(b *Builder)
|
||||||
|
|
||||||
|
// WithName sets builder name.
|
||||||
|
func WithName(name string) Option {
|
||||||
|
return func(b *Builder) {
|
||||||
|
b.opts.name = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithStore sets a store instance used at init.
|
||||||
|
func WithStore(txn *store.Txn) Option {
|
||||||
|
return func(b *Builder) {
|
||||||
|
b.opts.txn = txn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContextPathHash is used for determining pods in k8s driver instance.
|
||||||
|
func WithContextPathHash(contextPathHash string) Option {
|
||||||
|
return func(b *Builder) {
|
||||||
|
b.opts.contextPathHash = contextPathHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithSkippedValidation skips builder context validation.
|
||||||
|
func WithSkippedValidation() Option {
|
||||||
|
return func(b *Builder) {
|
||||||
|
b.opts.validate = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New initializes a new builder client
|
||||||
|
func New(dockerCli command.Cli, opts ...Option) (_ *Builder, err error) {
|
||||||
|
b := &Builder{
|
||||||
|
opts: builderOpts{
|
||||||
|
dockerCli: dockerCli,
|
||||||
|
validate: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.opts.txn == nil {
|
||||||
|
// if store instance is nil we create a short-lived one using the
|
||||||
|
// default store and ensure we release it on completion
|
||||||
|
var release func()
|
||||||
|
b.opts.txn, release, err = storeutil.GetStore(dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.opts.name != "" {
|
||||||
|
if b.NodeGroup, err = storeutil.GetNodeGroup(b.opts.txn, dockerCli, b.opts.name); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if b.NodeGroup, err = storeutil.GetCurrentInstance(b.opts.txn, dockerCli); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b.opts.validate {
|
||||||
|
if err = b.Validate(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates builder context
|
||||||
|
func (b *Builder) Validate() error {
|
||||||
|
if b.NodeGroup != nil && b.NodeGroup.DockerContext {
|
||||||
|
list, err := b.opts.dockerCli.ContextStore().List()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
currentContext := b.opts.dockerCli.CurrentContext()
|
||||||
|
for _, l := range list {
|
||||||
|
if l.Name == b.Name && l.Name != currentContext {
|
||||||
|
return errors.Errorf("use `docker --context=%s buildx` to switch to context %q", l.Name, l.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContextName returns builder context name if available.
|
||||||
|
func (b *Builder) ContextName() string {
|
||||||
|
ctxbuilders, err := b.opts.dockerCli.ContextStore().List()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for _, cb := range ctxbuilders {
|
||||||
|
if b.NodeGroup.Driver == "docker" && len(b.NodeGroup.Nodes) == 1 && b.NodeGroup.Nodes[0].Endpoint == cb.Name {
|
||||||
|
return cb.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ImageOpt returns registry auth configuration
|
||||||
|
func (b *Builder) ImageOpt() (imagetools.Opt, error) {
|
||||||
|
return storeutil.GetImageConfig(b.opts.dockerCli, b.NodeGroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Boot bootstrap a builder
|
||||||
|
func (b *Builder) Boot(ctx context.Context) (bool, error) {
|
||||||
|
toBoot := make([]int, 0, len(b.nodes))
|
||||||
|
for idx, d := range b.nodes {
|
||||||
|
if d.Err != nil || d.Driver == nil || d.DriverInfo == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if d.DriverInfo.Status != driver.Running {
|
||||||
|
toBoot = append(toBoot, idx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(toBoot) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
pw := progress.WithPrefix(printer, b.NodeGroup.Nodes[idx].Name, len(toBoot) > 1)
|
||||||
|
_, err := driver.Boot(ctx, baseCtx, b.nodes[idx].Driver, pw)
|
||||||
|
if err != nil {
|
||||||
|
b.nodes[idx].Err = err
|
||||||
|
errCh <- err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inactive checks if all nodes are inactive for this builder.
|
||||||
|
func (b *Builder) Inactive() bool {
|
||||||
|
for _, d := range b.nodes {
|
||||||
|
if d.DriverInfo != nil && d.DriverInfo.Status == driver.Running {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Err returns error if any.
|
||||||
|
func (b *Builder) Err() error {
|
||||||
|
return b.err
|
||||||
|
}
|
||||||
|
|
||||||
|
type driverFactory struct {
|
||||||
|
driver.Factory
|
||||||
|
once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
// Factory returns the driver factory.
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// empty driver means nodegroup was implicitly created as a default
|
||||||
|
// driver for a docker context and allows falling back to a
|
||||||
|
// docker-container driver for older daemon that doesn't support
|
||||||
|
// buildkit (< 18.06).
|
||||||
|
ep := b.NodeGroup.Nodes[0].Endpoint
|
||||||
|
var dockerapi *dockerutil.ClientAPI
|
||||||
|
dockerapi, err = dockerutil.NewClientAPI(b.opts.dockerCli, b.NodeGroup.Nodes[0].Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// check if endpoint is healthy is needed to determine the driver type.
|
||||||
|
// if this fails then can't continue with driver selection.
|
||||||
|
if _, err = dockerapi.Ping(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.driverFactory.Factory, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false, dialMeta)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b.Driver = b.driverFactory.Factory.Name()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
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()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
builders := make([]*Builder, len(storeng))
|
||||||
|
seen := make(map[string]struct{})
|
||||||
|
for i, ng := range storeng {
|
||||||
|
b, err := New(dockerCli,
|
||||||
|
WithName(ng.Name),
|
||||||
|
WithStore(txn),
|
||||||
|
WithSkippedValidation(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
builders[i] = b
|
||||||
|
seen[b.NodeGroup.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
contexts, err := dockerCli.ContextStore().List()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sort.Slice(contexts, func(i, j int) bool {
|
||||||
|
return contexts[i].Name < contexts[j].Name
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, c := range contexts {
|
||||||
|
// if a context has the same name as an instance from the store, do not
|
||||||
|
// add it to the builders list. An instance from the store takes
|
||||||
|
// precedence over context builders.
|
||||||
|
if _, ok := seen[c.Name]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b, err := New(dockerCli,
|
||||||
|
WithName(c.Name),
|
||||||
|
WithStore(txn),
|
||||||
|
WithSkippedValidation(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
builders = append(builders, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
291
builder/node.go
Normal file
291
builder/node.go
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
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"
|
||||||
|
"github.com/docker/buildx/store/storeutil"
|
||||||
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
|
"github.com/docker/buildx/util/imagetools"
|
||||||
|
"github.com/docker/buildx/util/platformutil"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/util/grpcerrors"
|
||||||
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
store.Node
|
||||||
|
Builder string
|
||||||
|
Driver *driver.DriverHandle
|
||||||
|
DriverInfo *driver.Info
|
||||||
|
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.
|
||||||
|
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, 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))
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if b.err == nil && err != nil {
|
||||||
|
b.err = err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
factory, err := b.Factory(ctx, lno.dialMeta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
imageopt, err := b.ImageOpt()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, n := range b.NodeGroup.Nodes {
|
||||||
|
func(i int, n store.Node) {
|
||||||
|
eg.Go(func() error {
|
||||||
|
node := Node{
|
||||||
|
Node: n,
|
||||||
|
ProxyConfig: storeutil.GetProxyConfig(b.opts.dockerCli),
|
||||||
|
Platforms: n.Platforms,
|
||||||
|
Builder: b.Name,
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
b.nodes[i] = node
|
||||||
|
}()
|
||||||
|
|
||||||
|
dockerapi, err := dockerutil.NewClientAPI(b.opts.dockerCli, n.Endpoint)
|
||||||
|
if err != nil {
|
||||||
|
node.Err = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
contextStore := b.opts.dockerCli.ContextStore()
|
||||||
|
|
||||||
|
var kcc driver.KubeClientConfig
|
||||||
|
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.ConfigFromEndpoint("default", contextStore)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tryToUseKubeConfigInCluster := false
|
||||||
|
if kcc == nil {
|
||||||
|
tryToUseKubeConfigInCluster = true
|
||||||
|
} else {
|
||||||
|
if _, err := kcc.ClientConfig(); err != nil {
|
||||||
|
tryToUseKubeConfigInCluster = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tryToUseKubeConfigInCluster {
|
||||||
|
kccInCluster := driver.KubeClientConfigInCluster{}
|
||||||
|
if _, err := kccInCluster.ClientConfig(); err == nil {
|
||||||
|
logrus.Debug("using kube config in cluster")
|
||||||
|
kcc = kccInCluster
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
node.Driver = d
|
||||||
|
node.ImageOpt = imageopt
|
||||||
|
|
||||||
|
if lno.data {
|
||||||
|
if err := node.loadData(ctx); err != nil {
|
||||||
|
node.Err = err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}(i, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This should be done in the routine loading driver data
|
||||||
|
if lno.data {
|
||||||
|
kubernetesDriverCount := 0
|
||||||
|
for _, d := range b.nodes {
|
||||||
|
if d.DriverInfo != nil && len(d.DriverInfo.DynamicNodes) > 0 {
|
||||||
|
kubernetesDriverCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isAllKubernetesDrivers := len(b.nodes) == kubernetesDriverCount
|
||||||
|
if isAllKubernetesDrivers {
|
||||||
|
var nodes []Node
|
||||||
|
var dynamicNodes []store.Node
|
||||||
|
for _, di := range b.nodes {
|
||||||
|
// dynamic nodes are used in Kubernetes driver.
|
||||||
|
// Kubernetes' pods are dynamically mapped to BuildKit Nodes.
|
||||||
|
if di.DriverInfo != nil && len(di.DriverInfo.DynamicNodes) > 0 {
|
||||||
|
for i := 0; i < len(di.DriverInfo.DynamicNodes); i++ {
|
||||||
|
diClone := di
|
||||||
|
if pl := di.DriverInfo.DynamicNodes[i].Platforms; len(pl) > 0 {
|
||||||
|
diClone.Platforms = pl
|
||||||
|
}
|
||||||
|
nodes = append(nodes, di)
|
||||||
|
}
|
||||||
|
dynamicNodes = append(dynamicNodes, di.DriverInfo.DynamicNodes...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not append (remove the static nodes in the store)
|
||||||
|
b.NodeGroup.Nodes = dynamicNodes
|
||||||
|
b.nodes = nodes
|
||||||
|
b.NodeGroup.Dynamic = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
info, err := n.Driver.Info(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n.DriverInfo = info
|
||||||
|
if n.DriverInfo.Status == driver.Running {
|
||||||
|
driverClient, err := n.Driver.Client(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
workers, err := driverClient.ListWorkers(ctx)
|
||||||
|
if err != nil {
|
||||||
|
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 {
|
||||||
|
if st, ok := grpcerrors.AsGRPCStatus(err); ok && st.Code() == codes.Unimplemented {
|
||||||
|
n.Version, err = n.Driver.Version(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
n.Version = inf.BuildkitVersion.Version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,59 +1,114 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/containerd/containerd/pkg/seed"
|
|
||||||
"github.com/docker/buildx/commands"
|
"github.com/docker/buildx/commands"
|
||||||
|
"github.com/docker/buildx/util/desktop"
|
||||||
"github.com/docker/buildx/version"
|
"github.com/docker/buildx/version"
|
||||||
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli-plugins/manager"
|
"github.com/docker/cli/cli-plugins/manager"
|
||||||
"github.com/docker/cli/cli-plugins/plugin"
|
"github.com/docker/cli/cli-plugins/plugin"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/debug"
|
||||||
cliflags "github.com/docker/cli/cli/flags"
|
cliflags "github.com/docker/cli/cli/flags"
|
||||||
"github.com/spf13/cobra"
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
|
"github.com/moby/buildkit/util/stack"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
|
||||||
|
//nolint:staticcheck // vendored dependencies may still use this
|
||||||
|
"github.com/containerd/containerd/pkg/seed"
|
||||||
|
|
||||||
// FIXME: "k8s.io/client-go/plugin/pkg/client/auth/azure" is excluded because of compilation error
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
|
||||||
|
|
||||||
_ "github.com/docker/buildx/driver/docker"
|
_ "github.com/docker/buildx/driver/docker"
|
||||||
_ "github.com/docker/buildx/driver/docker-container"
|
_ "github.com/docker/buildx/driver/docker-container"
|
||||||
_ "github.com/docker/buildx/driver/kubernetes"
|
_ "github.com/docker/buildx/driver/kubernetes"
|
||||||
|
_ "github.com/docker/buildx/driver/remote"
|
||||||
)
|
)
|
||||||
|
|
||||||
var experimental string
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
//nolint:staticcheck
|
||||||
seed.WithTimeAndRand()
|
seed.WithTimeAndRand()
|
||||||
|
|
||||||
|
stack.SetVersionInfo(version.Version, version.Revision)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runStandalone(cmd *command.DockerCli) error {
|
||||||
|
if err := cmd.Initialize(cliflags.NewClientOptions()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer flushMetrics(cmd)
|
||||||
|
|
||||||
|
rootCmd := commands.NewRootCmd(os.Args[0], false, cmd)
|
||||||
|
return rootCmd.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushMetrics will manually flush metrics from the configured
|
||||||
|
// meter provider. This is needed when running in standalone mode
|
||||||
|
// because the meter provider is initialized by the cli library,
|
||||||
|
// but the mechanism for forcing it to report is not presently
|
||||||
|
// exposed and not invoked when run in standalone mode.
|
||||||
|
// There are plans to fix that in the next release, but this is
|
||||||
|
// needed temporarily until the API for this is more thorough.
|
||||||
|
func flushMetrics(cmd *command.DockerCli) {
|
||||||
|
if mp, ok := cmd.MeterProvider().(command.MeterProvider); ok {
|
||||||
|
if err := mp.ForceFlush(context.Background()); err != nil {
|
||||||
|
otel.Handle(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runPlugin(cmd *command.DockerCli) error {
|
||||||
|
rootCmd := commands.NewRootCmd("buildx", true, cmd)
|
||||||
|
return plugin.RunPlugin(cmd, rootCmd, manager.Metadata{
|
||||||
|
SchemaVersion: "0.1.0",
|
||||||
|
Vendor: "Docker Inc.",
|
||||||
|
Version: version.Version,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if os.Getenv("DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND") == "" {
|
cmd, err := command.NewDockerCli()
|
||||||
if len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName {
|
if err != nil {
|
||||||
dockerCli, err := command.NewDockerCli()
|
fmt.Fprintln(os.Stderr, err)
|
||||||
if err != nil {
|
os.Exit(1)
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
opts := cliflags.NewClientOptions()
|
|
||||||
dockerCli.Initialize(opts)
|
|
||||||
rootCmd := commands.NewRootCmd(os.Args[0], false, dockerCli)
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.Run(func(dockerCli command.Cli) *cobra.Command {
|
if plugin.RunningStandalone() {
|
||||||
return commands.NewRootCmd("buildx", true, dockerCli)
|
err = runStandalone(cmd)
|
||||||
},
|
} else {
|
||||||
manager.Metadata{
|
err = runPlugin(cmd)
|
||||||
SchemaVersion: "0.1.0",
|
}
|
||||||
Vendor: "Docker Inc.",
|
if err == nil {
|
||||||
Version: version.Version,
|
return
|
||||||
Experimental: experimental != "",
|
}
|
||||||
})
|
|
||||||
|
if sterr, ok := err.(cli.StatusError); ok {
|
||||||
|
if sterr.Status != "" {
|
||||||
|
fmt.Fprintln(cmd.Err(), sterr.Status)
|
||||||
|
}
|
||||||
|
// StatusError should only be used for errors, and all errors should
|
||||||
|
// have a non-zero exit status, so never exit with 0
|
||||||
|
if sterr.StatusCode == 0 {
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
os.Exit(sterr.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range errdefs.Sources(err) {
|
||||||
|
s.Print(cmd.Err())
|
||||||
|
}
|
||||||
|
if debug.IsEnabled() {
|
||||||
|
fmt.Fprintf(cmd.Err(), "ERROR: %+v", stack.Formatter(err))
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(cmd.Err(), "ERROR: %v\n", err)
|
||||||
|
}
|
||||||
|
if ebr, ok := err.(*desktop.ErrorWithBuildRef); ok {
|
||||||
|
ebr.Print(cmd.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|||||||
19
cmd/buildx/tracing.go
Normal file
19
cmd/buildx/tracing.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/moby/buildkit/util/tracing/detect"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
|
||||||
|
_ "github.com/moby/buildkit/util/tracing/detect/delegated"
|
||||||
|
_ "github.com/moby/buildkit/util/tracing/env"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
detect.ServiceName = "buildx"
|
||||||
|
// do not log tracing errors to stdio
|
||||||
|
otel.SetErrorHandler(skipErrors{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type skipErrors struct{}
|
||||||
|
|
||||||
|
func (skipErrors) Handle(err error) {}
|
||||||
1
codecov.yml
Normal file
1
codecov.yml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
comment: false
|
||||||
320
commands/bake.go
320
commands/bake.go
@@ -1,36 +1,69 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containerd/console"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/docker/buildx/bake"
|
"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"
|
||||||
|
"github.com/docker/buildx/util/desktop"
|
||||||
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/buildx/util/tracing"
|
||||||
"github.com/docker/cli/cli/command"
|
"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/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type bakeOptions struct {
|
type bakeOptions struct {
|
||||||
files []string
|
files []string
|
||||||
printOnly bool
|
overrides []string
|
||||||
overrides []string
|
printOnly bool
|
||||||
commonOptions
|
sbom string
|
||||||
|
provenance string
|
||||||
|
|
||||||
|
builder string
|
||||||
|
metadataFile string
|
||||||
|
exportPush bool
|
||||||
|
exportLoad bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBake(dockerCli command.Cli, targets []string, in bakeOptions) error {
|
func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
|
||||||
ctx := appcontext.Context()
|
ctx, end, err := tracing.TraceCurrentCommand(ctx, "bake")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
end(err)
|
||||||
|
}()
|
||||||
|
|
||||||
if len(in.files) == 0 {
|
var url string
|
||||||
files, err := defaultFiles()
|
cmdContext := "cwd://"
|
||||||
if err != nil {
|
|
||||||
return err
|
if len(targets) > 0 {
|
||||||
|
if build.IsRemoteURL(targets[0]) {
|
||||||
|
url = targets[0]
|
||||||
|
targets = targets[1:]
|
||||||
|
if len(targets) > 0 {
|
||||||
|
if build.IsRemoteURL(targets[0]) {
|
||||||
|
cmdContext = targets[0]
|
||||||
|
targets = targets[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if len(files) == 0 {
|
|
||||||
return errors.Errorf("no docker-compose.yml or docker-bake.hcl found, specify build file with -f/--file")
|
|
||||||
}
|
|
||||||
in.files = files
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(targets) == 0 {
|
if len(targets) == 0 {
|
||||||
@@ -39,27 +72,129 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) error {
|
|||||||
|
|
||||||
overrides := in.overrides
|
overrides := in.overrides
|
||||||
if in.exportPush {
|
if in.exportPush {
|
||||||
if in.exportLoad {
|
overrides = append(overrides, "*.push=true")
|
||||||
return errors.Errorf("push and load may not be set together at the moment")
|
}
|
||||||
|
if in.exportLoad {
|
||||||
|
overrides = append(overrides, "*.load=true")
|
||||||
|
}
|
||||||
|
if cFlags.noCache != nil {
|
||||||
|
overrides = append(overrides, fmt.Sprintf("*.no-cache=%t", *cFlags.noCache))
|
||||||
|
}
|
||||||
|
if cFlags.pull != nil {
|
||||||
|
overrides = append(overrides, fmt.Sprintf("*.pull=%t", *cFlags.pull))
|
||||||
|
}
|
||||||
|
if in.sbom != "" {
|
||||||
|
overrides = append(overrides, fmt.Sprintf("*.attest=%s", buildflags.CanonicalizeAttest("sbom", in.sbom)))
|
||||||
|
}
|
||||||
|
if in.provenance != "" {
|
||||||
|
overrides = append(overrides, fmt.Sprintf("*.attest=%s", buildflags.CanonicalizeAttest("provenance", in.provenance)))
|
||||||
|
}
|
||||||
|
contextPathHash, _ := os.Getwd()
|
||||||
|
|
||||||
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var nodes []builder.Node
|
||||||
|
var progressConsoleDesc, progressTextDesc string
|
||||||
|
|
||||||
|
// instance only needed for reading remote bake files or building
|
||||||
|
if url != "" || !in.printOnly {
|
||||||
|
b, err := builder.New(dockerCli,
|
||||||
|
builder.WithName(in.builder),
|
||||||
|
builder.WithContextPathHash(contextPathHash),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
overrides = append(overrides, "*.output=type=registry")
|
if err = updateLastActivity(dockerCli, b.NodeGroup); err != nil {
|
||||||
} else if in.exportLoad {
|
return errors.Wrapf(err, "failed to update builder last activity time")
|
||||||
overrides = append(overrides, "*.output=type=docker")
|
}
|
||||||
}
|
nodes, err = b.LoadNodes(ctx)
|
||||||
if in.noCache != nil {
|
if err != nil {
|
||||||
overrides = append(overrides, fmt.Sprintf("*.no-cache=%t", *in.noCache))
|
return err
|
||||||
}
|
}
|
||||||
if in.pull != nil {
|
progressConsoleDesc = fmt.Sprintf("%s:%s", b.Driver, b.Name)
|
||||||
overrides = append(overrides, fmt.Sprintf("*.pull=%t", *in.pull))
|
progressTextDesc = fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver)
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := bake.ReadTargets(ctx, in.files, targets, overrides)
|
var term bool
|
||||||
|
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
|
||||||
|
term = true
|
||||||
|
}
|
||||||
|
|
||||||
|
progressMode := progressui.DisplayMode(cFlags.progress)
|
||||||
|
printer, err := progress.NewPrinter(ctx2, os.Stderr, progressMode,
|
||||||
|
progress.WithDesc(progressTextDesc, progressConsoleDesc),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if printer != nil {
|
||||||
|
err1 := printer.Wait()
|
||||||
|
if err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
if err == nil && progressMode != progressui.QuietMode && progressMode != progressui.RawJSONMode {
|
||||||
|
desktop.PrintBuildDetails(os.Stderr, printer.BuildRefs(), term)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
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
|
||||||
|
"BAKE_CMD_CONTEXT": cmdContext,
|
||||||
|
"BAKE_LOCAL_PLATFORM": platforms.DefaultString(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := os.Getenv("SOURCE_DATE_EPOCH"); v != "" {
|
||||||
|
// TODO: extract env var parsing to a method easily usable by library consumers
|
||||||
|
for _, t := range tgts {
|
||||||
|
if _, ok := t.Args["SOURCE_DATE_EPOCH"]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if t.Args == nil {
|
||||||
|
t.Args = map[string]*string{}
|
||||||
|
}
|
||||||
|
t.Args["SOURCE_DATE_EPOCH"] = &v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function can update target context string from the input so call before printOnly check
|
||||||
|
bo, err := bake.TargetsToBuildOpt(tgts, inp)
|
||||||
|
if err != nil {
|
||||||
|
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 {
|
if in.printOnly {
|
||||||
dt, err := json.MarshalIndent(map[string]map[string]*bake.Target{"target": m}, "", " ")
|
dt, err := json.MarshalIndent(def, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = printer.Wait()
|
||||||
|
printer = nil
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -67,40 +202,49 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
bo, err := bake.TargetsToBuildOpt(m)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := saveLocalStateGroup(dockerCli, groupRef, localstate.StateGroup{
|
||||||
contextPathHash, _ := os.Getwd()
|
Definition: dt,
|
||||||
|
Targets: targets,
|
||||||
return buildTargets(ctx, dockerCli, bo, in.progress, contextPathHash, in.builder)
|
Inputs: overrides,
|
||||||
}
|
Refs: refs,
|
||||||
|
}); err != nil {
|
||||||
func defaultFiles() ([]string, error) {
|
return err
|
||||||
fns := []string{
|
|
||||||
"docker-compose.yml", // support app
|
|
||||||
"docker-compose.yaml", // support app
|
|
||||||
"docker-bake.json",
|
|
||||||
"docker-bake.override.json",
|
|
||||||
"docker-bake.hcl",
|
|
||||||
"docker-bake.override.hcl",
|
|
||||||
}
|
}
|
||||||
out := make([]string, 0, len(fns))
|
|
||||||
for _, f := range fns {
|
resp, err := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer)
|
||||||
if _, err := os.Stat(f); err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(errors.Cause(err)) {
|
return wrapBuildError(err, true)
|
||||||
continue
|
}
|
||||||
}
|
|
||||||
return nil, err
|
if len(in.metadataFile) > 0 {
|
||||||
|
dt := make(map[string]interface{})
|
||||||
|
for t, r := range resp {
|
||||||
|
dt[t] = decodeExporterResponse(r.ExporterResponse)
|
||||||
|
}
|
||||||
|
if err := writeMetadataFile(in.metadataFile, dt); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
out = append(out, f)
|
|
||||||
}
|
}
|
||||||
return out, nil
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||||
var options bakeOptions
|
var options bakeOptions
|
||||||
|
var cFlags commonFlags
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "bake [OPTIONS] [TARGET...]",
|
Use: "bake [OPTIONS] [TARGET...]",
|
||||||
@@ -109,25 +253,81 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// reset to nil to avoid override is unset
|
// reset to nil to avoid override is unset
|
||||||
if !cmd.Flags().Lookup("no-cache").Changed {
|
if !cmd.Flags().Lookup("no-cache").Changed {
|
||||||
options.noCache = nil
|
cFlags.noCache = nil
|
||||||
}
|
}
|
||||||
if !cmd.Flags().Lookup("pull").Changed {
|
if !cmd.Flags().Lookup("pull").Changed {
|
||||||
options.pull = nil
|
cFlags.pull = nil
|
||||||
}
|
}
|
||||||
options.commonOptions.builder = rootOpts.builder
|
options.builder = rootOpts.builder
|
||||||
return runBake(dockerCli, args, options)
|
options.metadataFile = cFlags.metadataFile
|
||||||
|
// Other common flags (noCache, pull and progress) are processed in runBake function.
|
||||||
|
return runBake(cmd.Context(), dockerCli, args, options, cFlags)
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: completion.BakeTargets(options.files),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Build definition file")
|
flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Build definition file")
|
||||||
|
flags.BoolVar(&options.exportLoad, "load", false, `Shorthand for "--set=*.output=type=docker"`)
|
||||||
flags.BoolVar(&options.printOnly, "print", false, "Print the options without building")
|
flags.BoolVar(&options.printOnly, "print", false, "Print the options without building")
|
||||||
flags.StringArrayVar(&options.overrides, "set", nil, "Override target value (eg: targetpattern.key=value)")
|
flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--set=*.output=type=registry"`)
|
||||||
flags.BoolVar(&options.exportPush, "push", false, "Shorthand for --set=*.output=type=registry")
|
flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--set=*.attest=type=sbom"`)
|
||||||
flags.BoolVar(&options.exportLoad, "load", false, "Shorthand for --set=*.output=type=docker")
|
flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--set=*.attest=type=provenance"`)
|
||||||
|
flags.StringArrayVar(&options.overrides, "set", nil, `Override target value (e.g., "targetpattern.key=value")`)
|
||||||
|
|
||||||
commonBuildFlags(&options.commonOptions, flags)
|
commonBuildFlags(&cFlags, flags)
|
||||||
|
|
||||||
return cmd
|
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
|
||||||
|
}
|
||||||
|
|||||||
1172
commands/build.go
1172
commands/build.go
File diff suppressed because it is too large
Load Diff
@@ -1,180 +1,94 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/csv"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
"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/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/google/shlex"
|
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type createOptions struct {
|
type createOptions struct {
|
||||||
name string
|
name string
|
||||||
driver string
|
driver string
|
||||||
nodeName string
|
nodeName string
|
||||||
platform []string
|
platform []string
|
||||||
actionAppend bool
|
actionAppend bool
|
||||||
actionLeave bool
|
actionLeave bool
|
||||||
use bool
|
use bool
|
||||||
flags string
|
driverOpts []string
|
||||||
configFile string
|
buildkitdFlags string
|
||||||
driverOpts []string
|
buildkitdConfigFile string
|
||||||
|
bootstrap bool
|
||||||
// upgrade bool // perform upgrade of the driver
|
// upgrade bool // perform upgrade of the driver
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
func runCreate(ctx context.Context, dockerCli command.Cli, in createOptions, args []string) error {
|
||||||
ctx := appcontext.Context()
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
|
|
||||||
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")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
driverName := in.driver
|
|
||||||
if driverName == "" {
|
|
||||||
f, err := driver.GetDefaultFactory(ctx, dockerCli.Client(), true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if f == nil {
|
|
||||||
return errors.Errorf("no valid drivers found")
|
|
||||||
}
|
|
||||||
driverName = f.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
if driver.GetFactory(driverName, true) == nil {
|
|
||||||
return errors.Errorf("failed to find driver %q", in.driver)
|
|
||||||
}
|
|
||||||
|
|
||||||
txn, release, err := getStore(dockerCli)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Ensure the file lock gets released no matter what happens.
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
name := in.name
|
if in.actionLeave {
|
||||||
if name == "" {
|
return builder.Leave(ctx, txn, dockerCli, builder.LeaveOpts{
|
||||||
name, err = store.GenerateName(txn)
|
Name: in.name,
|
||||||
if err != nil {
|
NodeName: in.nodeName,
|
||||||
return err
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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", name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ng != nil {
|
|
||||||
if in.nodeName == "" && !in.actionAppend {
|
|
||||||
return errors.Errorf("existing instance for %s but no append mode, specify --node to make changes for existing instances", name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ng == nil {
|
|
||||||
ng = &store.NodeGroup{
|
|
||||||
Name: name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ng.Driver == "" || in.driver != "" {
|
|
||||||
ng.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")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ep string
|
var ep string
|
||||||
if in.actionLeave {
|
if len(args) > 0 {
|
||||||
if err := ng.Leave(in.nodeName); err != nil {
|
ep = args[0]
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if len(args) > 0 {
|
|
||||||
ep, err = validateEndpoint(dockerCli, args[0])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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 = getCurrentEndpoint(dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m, err := csvToMap(in.driverOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ng.Update(in.nodeName, ep, in.platform, len(args) > 0, in.actionAppend, flags, in.configFile, m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := txn.Save(ng); err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if in.use && ep != "" {
|
// The store is no longer used from this point.
|
||||||
current, err := getCurrentEndpoint(dockerCli)
|
// Release it so we aren't holding the file lock during the boot.
|
||||||
if err != nil {
|
release()
|
||||||
return err
|
|
||||||
}
|
if in.bootstrap {
|
||||||
if err := txn.SetCurrent(current, ng.Name, false, false); err != nil {
|
if _, err = b.Boot(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s\n", ng.Name)
|
fmt.Printf("%s\n", b.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createCmd(dockerCli command.Cli) *cobra.Command {
|
func createCmd(dockerCli command.Cli) *cobra.Command {
|
||||||
var options createOptions
|
var options createOptions
|
||||||
|
|
||||||
var drivers []string
|
var drivers bytes.Buffer
|
||||||
for s := range driver.GetFactories() {
|
for _, d := range driver.GetFactories(true) {
|
||||||
drivers = append(drivers, s)
|
if len(drivers.String()) > 0 {
|
||||||
|
drivers.WriteString(", ")
|
||||||
|
}
|
||||||
|
drivers.WriteString(fmt.Sprintf(`"%s"`, d.Name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
@@ -182,44 +96,32 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
Short: "Create a new builder instance",
|
Short: "Create a new builder instance",
|
||||||
Args: cli.RequiresMaxArgs(1),
|
Args: cli.RequiresMaxArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runCreate(dockerCli, options, args)
|
return runCreate(cmd.Context(), dockerCli, options, args)
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.StringVar(&options.name, "name", "", "Builder instance name")
|
flags.StringVar(&options.name, "name", "", "Builder instance name")
|
||||||
flags.StringVar(&options.driver, "driver", "", fmt.Sprintf("Driver to use (available: %v)", drivers))
|
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.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.platform, "platform", []string{}, "Fixed platforms for current node")
|
||||||
flags.StringArrayVar(&options.driverOpts, "driver-opt", []string{}, "Options for the driver")
|
flags.StringArrayVar(&options.driverOpts, "driver-opt", []string{}, "Options for the driver")
|
||||||
|
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.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.actionLeave, "leave", false, "Remove a node from builder instead of changing it")
|
||||||
flags.BoolVar(&options.use, "use", false, "Set the current builder instance")
|
flags.BoolVar(&options.use, "use", false, "Set the current builder instance")
|
||||||
|
|
||||||
_ = flags
|
// hide builder persistent flag for this command
|
||||||
|
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func csvToMap(in []string) (map[string]string, error) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|||||||
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,19 +1,22 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
|
"github.com/docker/go-units"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/tonistiigi/units"
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,33 +26,35 @@ type duOptions struct {
|
|||||||
verbose bool
|
verbose bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
|
func runDiskUsage(ctx context.Context, dockerCli command.Cli, opts duOptions) error {
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
pi, err := toBuildkitPruneInfo(opts.filter.Value())
|
pi, err := toBuildkitPruneInfo(opts.filter.Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dis, err := getInstanceOrDefault(ctx, dockerCli, opts.builder, "")
|
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, di := range dis {
|
nodes, err := b.LoadNodes(ctx)
|
||||||
if di.Err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node.Err != nil {
|
||||||
|
return node.Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make([][]*client.UsageInfo, len(dis))
|
out := make([][]*client.UsageInfo, len(nodes))
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
for i, di := range dis {
|
for i, node := range nodes {
|
||||||
func(i int, di build.DriverInfo) {
|
func(i int, node builder.Node) {
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
if di.Driver != nil {
|
if node.Driver != nil {
|
||||||
c, err := di.Driver.Client(ctx)
|
c, err := node.Driver.Client(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -62,7 +67,7 @@ func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}(i, di)
|
}(i, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
if err := eg.Wait(); err != nil {
|
||||||
@@ -107,9 +112,9 @@ func duCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.builder = rootOpts.builder
|
options.builder = rootOpts.builder
|
||||||
return runDiskUsage(dockerCli, options)
|
return runDiskUsage(cmd.Context(), dockerCli, options)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{"version": "1.00"},
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
@@ -126,20 +131,20 @@ func printKV(w io.Writer, k string, v interface{}) {
|
|||||||
func printVerbose(tw *tabwriter.Writer, du []*client.UsageInfo) {
|
func printVerbose(tw *tabwriter.Writer, du []*client.UsageInfo) {
|
||||||
for _, di := range du {
|
for _, di := range du {
|
||||||
printKV(tw, "ID", di.ID)
|
printKV(tw, "ID", di.ID)
|
||||||
if di.Parent != "" {
|
if len(di.Parents) != 0 {
|
||||||
printKV(tw, "Parent", di.Parent)
|
printKV(tw, "Parent", strings.Join(di.Parents, ","))
|
||||||
}
|
}
|
||||||
printKV(tw, "Created at", di.CreatedAt)
|
printKV(tw, "Created at", di.CreatedAt)
|
||||||
printKV(tw, "Mutable", di.Mutable)
|
printKV(tw, "Mutable", di.Mutable)
|
||||||
printKV(tw, "Reclaimable", !di.InUse)
|
printKV(tw, "Reclaimable", !di.InUse)
|
||||||
printKV(tw, "Shared", di.Shared)
|
printKV(tw, "Shared", di.Shared)
|
||||||
printKV(tw, "Size", fmt.Sprintf("%.2f", units.Bytes(di.Size)))
|
printKV(tw, "Size", units.HumanSize(float64(di.Size)))
|
||||||
if di.Description != "" {
|
if di.Description != "" {
|
||||||
printKV(tw, "Description", di.Description)
|
printKV(tw, "Description", di.Description)
|
||||||
}
|
}
|
||||||
printKV(tw, "Usage count", di.UsageCount)
|
printKV(tw, "Usage count", di.UsageCount)
|
||||||
if di.LastUsedAt != nil {
|
if di.LastUsedAt != nil {
|
||||||
printKV(tw, "Last used", di.LastUsedAt)
|
printKV(tw, "Last used", units.HumanDuration(time.Since(*di.LastUsedAt))+" ago")
|
||||||
}
|
}
|
||||||
if di.RecordType != "" {
|
if di.RecordType != "" {
|
||||||
printKV(tw, "Type", di.RecordType)
|
printKV(tw, "Type", di.RecordType)
|
||||||
@@ -160,11 +165,15 @@ func printTableRow(tw *tabwriter.Writer, di *client.UsageInfo) {
|
|||||||
if di.Mutable {
|
if di.Mutable {
|
||||||
id += "*"
|
id += "*"
|
||||||
}
|
}
|
||||||
size := fmt.Sprintf("%.2f", units.Bytes(di.Size))
|
size := units.HumanSize(float64(di.Size))
|
||||||
if di.Shared {
|
if di.Shared {
|
||||||
size += "*"
|
size += "*"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(tw, "%-71s\t%-11v\t%s\t\n", id, !di.InUse, size)
|
lastAccessed := ""
|
||||||
|
if di.LastUsedAt != nil {
|
||||||
|
lastAccessed = units.HumanDuration(time.Since(*di.LastUsedAt)) + " ago"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(tw, "%-40s\t%-5v\t%-10s\t%s\n", id, !di.InUse, size, lastAccessed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printSummary(tw *tabwriter.Writer, dus [][]*client.UsageInfo) {
|
func printSummary(tw *tabwriter.Writer, dus [][]*client.UsageInfo) {
|
||||||
@@ -186,14 +195,12 @@ func printSummary(tw *tabwriter.Writer, dus [][]*client.UsageInfo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
|
|
||||||
|
|
||||||
if shared > 0 {
|
if shared > 0 {
|
||||||
fmt.Fprintf(tw, "Shared:\t%.2f\n", units.Bytes(shared))
|
fmt.Fprintf(tw, "Shared:\t%s\n", units.HumanSize(float64(shared)))
|
||||||
fmt.Fprintf(tw, "Private:\t%.2f\n", units.Bytes(total-shared))
|
fmt.Fprintf(tw, "Private:\t%s\n", units.HumanSize(float64(total-shared)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(tw, "Reclaimable:\t%.2f\n", units.Bytes(reclaimable))
|
fmt.Fprintf(tw, "Reclaimable:\t%s\n", units.HumanSize(float64(reclaimable)))
|
||||||
fmt.Fprintf(tw, "Total:\t%.2f\n", units.Bytes(total))
|
fmt.Fprintf(tw, "Total:\t%s\n", units.HumanSize(float64(total)))
|
||||||
tw.Flush()
|
tw.Flush()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"strings"
|
"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/imagetools"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/distribution/reference"
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -18,13 +22,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type createOptions struct {
|
type createOptions struct {
|
||||||
|
builder string
|
||||||
files []string
|
files []string
|
||||||
tags []string
|
tags []string
|
||||||
|
annotations []string
|
||||||
dryrun bool
|
dryrun bool
|
||||||
actionAppend 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 {
|
if len(args) == 0 && len(in.files) == 0 {
|
||||||
return errors.Errorf("no sources specified")
|
return errors.Errorf("no sources specified")
|
||||||
}
|
}
|
||||||
@@ -35,7 +42,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
|
|
||||||
fileArgs := make([]string, len(in.files))
|
fileArgs := make([]string, len(in.files))
|
||||||
for i, f := range in.files {
|
for i, f := range in.files {
|
||||||
dt, err := ioutil.ReadFile(f)
|
dt, err := os.ReadFile(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -75,35 +82,46 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
if len(repos) == 0 {
|
if len(repos) == 0 {
|
||||||
return errors.Errorf("no repositories specified, please set a reference in tag or source")
|
return errors.Errorf("no repositories specified, please set a reference in tag or source")
|
||||||
}
|
}
|
||||||
if len(repos) > 1 {
|
|
||||||
return errors.Errorf("multiple repositories currently not supported, found %v", repos)
|
|
||||||
}
|
|
||||||
|
|
||||||
var repo string
|
var defaultRepo *string
|
||||||
for r := range repos {
|
if len(repos) == 1 {
|
||||||
repo = r
|
for repo := range repos {
|
||||||
}
|
defaultRepo = &repo
|
||||||
|
|
||||||
for i, s := range srcs {
|
|
||||||
if s.Ref == nil && s.Desc.MediaType == "" && s.Desc.Digest != "" {
|
|
||||||
n, err := reference.ParseNormalizedNamed(repo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
r, err := reference.WithDigest(n, s.Desc.Digest)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
srcs[i].Ref = r
|
|
||||||
sourceRefs = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := appcontext.Context()
|
for i, s := range srcs {
|
||||||
|
if s.Ref == nil {
|
||||||
|
if defaultRepo == nil {
|
||||||
|
return errors.Errorf("multiple repositories specified, cannot infer repository for %q", args[i])
|
||||||
|
}
|
||||||
|
n, err := reference.ParseNormalizedNamed(*defaultRepo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if s.Desc.MediaType == "" && s.Desc.Digest != "" {
|
||||||
|
r, err := reference.WithDigest(n, s.Desc.Digest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srcs[i].Ref = r
|
||||||
|
sourceRefs = true
|
||||||
|
} else {
|
||||||
|
srcs[i].Ref = reference.TagNameOnly(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
r := imagetools.New(imagetools.Opt{
|
b, err := builder.New(dockerCli, builder.WithName(in.builder))
|
||||||
Auth: dockerCli.ConfigFile(),
|
if err != nil {
|
||||||
})
|
return err
|
||||||
|
}
|
||||||
|
imageopt, err := b.ImageOpt()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := imagetools.New(imageopt)
|
||||||
|
|
||||||
if sourceRefs {
|
if sourceRefs {
|
||||||
eg, ctx2 := errgroup.WithContext(ctx)
|
eg, ctx2 := errgroup.WithContext(ctx)
|
||||||
@@ -117,8 +135,15 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
srcs[i].Ref = nil
|
if srcs[i].Desc.Digest == "" {
|
||||||
srcs[i].Desc = desc
|
srcs[i].Desc = desc
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
srcs[i].Desc, err = mergeDesc(desc, srcs[i].Desc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}(i)
|
}(i)
|
||||||
@@ -128,12 +153,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
descs := make([]ocispec.Descriptor, len(srcs))
|
dt, desc, err := r.Combine(ctx, srcs, in.annotations)
|
||||||
for i := range descs {
|
|
||||||
descs[i] = srcs[i].Desc
|
|
||||||
}
|
|
||||||
|
|
||||||
dt, desc, err := r.Combine(ctx, repo, descs)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -144,31 +164,58 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// new resolver cause need new auth
|
// new resolver cause need new auth
|
||||||
r = imagetools.New(imagetools.Opt{
|
r = imagetools.New(imageopt)
|
||||||
Auth: dockerCli.ConfigFile(),
|
|
||||||
})
|
|
||||||
|
|
||||||
for _, t := range tags {
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
if err := r.Push(ctx, t, desc, dt); err != nil {
|
defer cancel()
|
||||||
return err
|
printer, err := progress.NewPrinter(ctx2, os.Stderr, progressui.DisplayMode(in.progress))
|
||||||
}
|
if err != nil {
|
||||||
fmt.Println(t.String())
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
|
pw := progress.WithPrefix(printer, "internal", true)
|
||||||
|
|
||||||
|
for _, t := range tags {
|
||||||
|
t := t
|
||||||
|
eg.Go(func() error {
|
||||||
|
return progress.Wrap(fmt.Sprintf("pushing %s", t.String()), pw.Write, func(sub progress.SubLogger) error {
|
||||||
|
eg2, _ := errgroup.WithContext(ctx)
|
||||||
|
for _, s := range srcs {
|
||||||
|
if reference.Domain(s.Ref) == reference.Domain(t) && reference.Path(s.Ref) == reference.Path(t) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s := s
|
||||||
|
eg2.Go(func() error {
|
||||||
|
sub.Log(1, []byte(fmt.Sprintf("copying %s from %s to %s\n", s.Desc.Digest.String(), s.Ref.String(), t.String())))
|
||||||
|
return r.Copy(ctx, s, t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := eg2.Wait(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sub.Log(1, []byte(fmt.Sprintf("pushing %s to %s\n", desc.Digest.String(), t.String())))
|
||||||
|
return r.Push(ctx, t, desc, dt)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err = eg.Wait()
|
||||||
|
err1 := printer.Wait()
|
||||||
|
if err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type src struct {
|
func parseSources(in []string) ([]*imagetools.Source, error) {
|
||||||
Desc ocispec.Descriptor
|
out := make([]*imagetools.Source, len(in))
|
||||||
Ref reference.Named
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSources(in []string) ([]*src, error) {
|
|
||||||
out := make([]*src, len(in))
|
|
||||||
for i, in := range in {
|
for i, in := range in {
|
||||||
s, err := parseSource(in)
|
s, err := parseSource(in)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "failed to parse source %q, valid sources are digests, refereces and descriptors", in)
|
return nil, errors.Wrapf(err, "failed to parse source %q, valid sources are digests, references and descriptors", in)
|
||||||
}
|
}
|
||||||
out[i] = s
|
out[i] = s
|
||||||
}
|
}
|
||||||
@@ -187,11 +234,11 @@ func parseRefs(in []string) ([]reference.Named, error) {
|
|||||||
return refs, nil
|
return refs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSource(in string) (*src, error) {
|
func parseSource(in string) (*imagetools.Source, error) {
|
||||||
// source can be a digest, reference or a descriptor JSON
|
// source can be a digest, reference or a descriptor JSON
|
||||||
dgst, err := digest.Parse(in)
|
dgst, err := digest.Parse(in)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &src{
|
return &imagetools.Source{
|
||||||
Desc: ocispec.Descriptor{
|
Desc: ocispec.Descriptor{
|
||||||
Digest: dgst,
|
Digest: dgst,
|
||||||
},
|
},
|
||||||
@@ -202,39 +249,56 @@ func parseSource(in string) (*src, error) {
|
|||||||
|
|
||||||
ref, err := reference.ParseNormalizedNamed(in)
|
ref, err := reference.ParseNormalizedNamed(in)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &src{
|
return &imagetools.Source{
|
||||||
Ref: ref,
|
Ref: ref,
|
||||||
}, nil
|
}, nil
|
||||||
} else if !strings.HasPrefix(in, "{") {
|
} else if !strings.HasPrefix(in, "{") {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var s src
|
var s imagetools.Source
|
||||||
if err := json.Unmarshal([]byte(in), &s.Desc); err != nil {
|
if err := json.Unmarshal([]byte(in), &s.Desc); err != nil {
|
||||||
return nil, errors.WithStack(err)
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
return &s, nil
|
return &s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createCmd(dockerCli command.Cli) *cobra.Command {
|
func createCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
||||||
var options createOptions
|
var options createOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "create [OPTIONS] [SOURCE] [SOURCE...]",
|
Use: "create [OPTIONS] [SOURCE] [SOURCE...]",
|
||||||
Short: "Create a new image based on source images",
|
Short: "Create a new image based on source images",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runCreate(dockerCli, options, args)
|
options.builder = *opts.Builder
|
||||||
|
return runCreate(cmd.Context(), dockerCli, options, args)
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Read source descriptor from file")
|
flags.StringArrayVarP(&options.files, "file", "f", []string{}, "Read source descriptor from file")
|
||||||
flags.StringArrayVarP(&options.tags, "tag", "t", []string{}, "Set reference for new image")
|
flags.StringArrayVarP(&options.tags, "tag", "t", []string{}, "Set reference for new image")
|
||||||
flags.BoolVar(&options.dryrun, "dry-run", false, "Show final image instead of pushing")
|
flags.BoolVar(&options.dryrun, "dry-run", false, "Show final image instead of pushing")
|
||||||
flags.BoolVar(&options.actionAppend, "append", false, "Append to existing manifest")
|
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
|
flags.StringArrayVarP(&options.annotations, "annotation", "", []string{}, "Add annotation to the image")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mergeDesc(d1, d2 ocispec.Descriptor) (ocispec.Descriptor, error) {
|
||||||
|
if d2.Size != 0 && d1.Size != d2.Size {
|
||||||
|
return ocispec.Descriptor{}, errors.Errorf("invalid size mismatch for %s, %d != %d", d1.Digest, d2.Size, d1.Size)
|
||||||
|
}
|
||||||
|
if d2.MediaType != "" {
|
||||||
|
d1.MediaType = d2.MediaType
|
||||||
|
}
|
||||||
|
if len(d2.Annotations) != 0 {
|
||||||
|
d1.Annotations = d2.Annotations // no merge so support removes
|
||||||
|
}
|
||||||
|
if d2.Platform != nil {
|
||||||
|
d1.Platform = d2.Platform // missing items filled in later from image config
|
||||||
|
}
|
||||||
|
return d1, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,68 +1,66 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/containerd/containerd/images"
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/buildx/util/imagetools"
|
"github.com/docker/buildx/util/imagetools"
|
||||||
|
"github.com/docker/cli-docs-tool/annotation"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/pkg/errors"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type inspectOptions struct {
|
type inspectOptions struct {
|
||||||
raw bool
|
builder string
|
||||||
|
format string
|
||||||
|
raw bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runInspect(dockerCli command.Cli, in inspectOptions, name string) error {
|
func runInspect(ctx context.Context, dockerCli command.Cli, in inspectOptions, name string) error {
|
||||||
ctx := appcontext.Context()
|
if in.format != "" && in.raw {
|
||||||
|
return errors.Errorf("format and raw cannot be used together")
|
||||||
|
}
|
||||||
|
|
||||||
r := imagetools.New(imagetools.Opt{
|
b, err := builder.New(dockerCli, builder.WithName(in.builder))
|
||||||
Auth: dockerCli.ConfigFile(),
|
if err != nil {
|
||||||
})
|
return err
|
||||||
|
}
|
||||||
dt, desc, err := r.Get(ctx, name)
|
imageopt, err := b.ImageOpt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if in.raw {
|
p, err := imagetools.NewPrinter(ctx, imageopt, name, in.format)
|
||||||
fmt.Printf("%s", dt) // avoid newline to keep digest
|
if err != nil {
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch desc.MediaType {
|
return p.Print(in.raw, dockerCli.Out())
|
||||||
// case images.MediaTypeDockerSchema2Manifest, specs.MediaTypeImageManifest:
|
|
||||||
// TODO: handle distribution manifest and schema1
|
|
||||||
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
|
||||||
imagetools.PrintManifestList(dt, desc, name, os.Stdout)
|
|
||||||
default:
|
|
||||||
fmt.Printf("%s\n", dt)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func inspectCmd(dockerCli command.Cli) *cobra.Command {
|
func inspectCmd(dockerCli command.Cli, rootOpts RootOptions) *cobra.Command {
|
||||||
var options inspectOptions
|
var options inspectOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "inspect [OPTIONS] NAME",
|
Use: "inspect [OPTIONS] NAME",
|
||||||
Short: "Show details of image in the registry",
|
Short: "Show details of an image in the registry",
|
||||||
Args: cli.ExactArgs(1),
|
Args: cli.ExactArgs(1),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runInspect(dockerCli, options, args[0])
|
options.builder = *rootOpts.Builder
|
||||||
|
return runInspect(cmd.Context(), dockerCli, options, args[0])
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.BoolVar(&options.raw, "raw", false, "Show original JSON manifest")
|
flags.StringVar(&options.format, "format", "", "Format the output using the given Go template")
|
||||||
|
flags.SetAnnotation("format", annotation.DefaultValue, []string{`"{{.Manifest}}"`})
|
||||||
|
|
||||||
_ = flags
|
flags.BoolVar(&options.raw, "raw", false, "Show original, unformatted JSON manifest")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RootCmd(dockerCli command.Cli) *cobra.Command {
|
type RootOptions struct {
|
||||||
|
Builder *string
|
||||||
|
}
|
||||||
|
|
||||||
|
func RootCmd(dockerCli command.Cli, opts RootOptions) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "imagetools",
|
Use: "imagetools",
|
||||||
Short: "Commands to work on images in registry",
|
Short: "Commands to work on images in registry",
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
inspectCmd(dockerCli),
|
createCmd(dockerCli, opts),
|
||||||
createCmd(dockerCli),
|
inspectCmd(dockerCli, opts),
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
@@ -4,21 +4,20 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/docker/cli/cli/debug"
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
"github.com/docker/go-units"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type inspectOptions struct {
|
type inspectOptions struct {
|
||||||
@@ -26,99 +25,107 @@ type inspectOptions struct {
|
|||||||
builder string
|
builder string
|
||||||
}
|
}
|
||||||
|
|
||||||
type dinfo struct {
|
func runInspect(ctx context.Context, dockerCli command.Cli, in inspectOptions) error {
|
||||||
di *build.DriverInfo
|
b, err := builder.New(dockerCli,
|
||||||
info *driver.Info
|
builder.WithName(in.builder),
|
||||||
platforms []specs.Platform
|
builder.WithSkippedValidation(),
|
||||||
err error
|
)
|
||||||
}
|
|
||||||
|
|
||||||
type nginfo struct {
|
|
||||||
ng *store.NodeGroup
|
|
||||||
drivers []dinfo
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
txn, release, err := getStore(dockerCli)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer release()
|
|
||||||
|
|
||||||
var ng *store.NodeGroup
|
|
||||||
|
|
||||||
if in.builder != "" {
|
|
||||||
ng, err = getNodeGroup(txn, dockerCli, in.builder)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ng, err = getCurrentInstance(txn, dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ng == nil {
|
|
||||||
ng = &store.NodeGroup{
|
|
||||||
Name: "default",
|
|
||||||
Nodes: []store.Node{{
|
|
||||||
Name: "default",
|
|
||||||
Endpoint: "default",
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngi := &nginfo{ng: ng}
|
|
||||||
|
|
||||||
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = loadNodeGroupData(timeoutCtx, dockerCli, ngi)
|
nodes, err := b.LoadNodes(timeoutCtx, builder.WithData())
|
||||||
|
|
||||||
if in.bootstrap {
|
if in.bootstrap {
|
||||||
var ok bool
|
var ok bool
|
||||||
ok, err = boot(ctx, ngi)
|
ok, err = b.Boot(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
ngi = &nginfo{ng: ng}
|
nodes, err = b.LoadNodes(timeoutCtx, builder.WithData())
|
||||||
err = loadNodeGroupData(ctx, dockerCli, ngi)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
||||||
fmt.Fprintf(w, "Name:\t%s\n", ngi.ng.Name)
|
fmt.Fprintf(w, "Name:\t%s\n", b.Name)
|
||||||
fmt.Fprintf(w, "Driver:\t%s\n", ngi.ng.Driver)
|
fmt.Fprintf(w, "Driver:\t%s\n", b.Driver)
|
||||||
|
if !b.NodeGroup.LastActivity.IsZero() {
|
||||||
|
fmt.Fprintf(w, "Last Activity:\t%v\n", b.NodeGroup.LastActivity)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||||
} else if ngi.err != nil {
|
} else if b.Err() != nil {
|
||||||
fmt.Fprintf(w, "Error:\t%s\n", ngi.err.Error())
|
fmt.Fprintf(w, "Error:\t%s\n", b.Err().Error())
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
fmt.Fprintln(w, "")
|
fmt.Fprintln(w, "")
|
||||||
fmt.Fprintln(w, "Nodes:")
|
fmt.Fprintln(w, "Nodes:")
|
||||||
|
|
||||||
for i, n := range ngi.ng.Nodes {
|
for i, n := range nodes {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
fmt.Fprintln(w, "")
|
fmt.Fprintln(w, "")
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "Name:\t%s\n", n.Name)
|
fmt.Fprintf(w, "Name:\t%s\n", n.Name)
|
||||||
fmt.Fprintf(w, "Endpoint:\t%s\n", n.Endpoint)
|
fmt.Fprintf(w, "Endpoint:\t%s\n", n.Endpoint)
|
||||||
if err := ngi.drivers[i].di.Err; err != nil {
|
|
||||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
var driverOpts []string
|
||||||
} else if err := ngi.drivers[i].err; err != nil {
|
for k, v := range n.DriverOpts {
|
||||||
|
driverOpts = append(driverOpts, fmt.Sprintf("%s=%q", k, v))
|
||||||
|
}
|
||||||
|
if len(driverOpts) > 0 {
|
||||||
|
fmt.Fprintf(w, "Driver Options:\t%s\n", strings.Join(driverOpts, " "))
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := n.Err; err != nil {
|
||||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, "Status:\t%s\n", ngi.drivers[i].info.Status)
|
fmt.Fprintf(w, "Status:\t%s\n", nodes[i].DriverInfo.Status)
|
||||||
if len(n.Flags) > 0 {
|
if len(n.BuildkitdFlags) > 0 {
|
||||||
fmt.Fprintf(w, "Flags:\t%s\n", strings.Join(n.Flags, " "))
|
fmt.Fprintf(w, "BuildKit daemon flags:\t%s\n", strings.Join(n.BuildkitdFlags, " "))
|
||||||
|
}
|
||||||
|
if 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, ", "))
|
||||||
|
}
|
||||||
|
if debug.IsEnabled() {
|
||||||
|
fmt.Fprintf(w, "Features:\n")
|
||||||
|
features := nodes[i].Driver.Features(ctx)
|
||||||
|
featKeys := make([]string, 0, len(features))
|
||||||
|
for k := range features {
|
||||||
|
featKeys = append(featKeys, string(k))
|
||||||
|
}
|
||||||
|
sort.Strings(featKeys)
|
||||||
|
for _, k := range featKeys {
|
||||||
|
fmt.Fprintf(w, "\t%s:\t%t\n", k, features[driver.Feature(k)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(nodes[i].Labels) > 0 {
|
||||||
|
fmt.Fprintf(w, "Labels:\n")
|
||||||
|
for _, k := range sortedKeys(nodes[i].Labels) {
|
||||||
|
v := nodes[i].Labels[k]
|
||||||
|
fmt.Fprintf(w, "\t%s:\t%s\n", k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for ri, rule := range nodes[i].GCPolicy {
|
||||||
|
fmt.Fprintf(w, "GC Policy rule#%d:\n", ri)
|
||||||
|
fmt.Fprintf(w, "\tAll:\t%v\n", rule.All)
|
||||||
|
if len(rule.Filter) > 0 {
|
||||||
|
fmt.Fprintf(w, "\tFilters:\t%s\n", strings.Join(rule.Filter, " "))
|
||||||
|
}
|
||||||
|
if rule.KeepDuration > 0 {
|
||||||
|
fmt.Fprintf(w, "\tKeep Duration:\t%v\n", rule.KeepDuration.String())
|
||||||
|
}
|
||||||
|
if rule.KeepBytes > 0 {
|
||||||
|
fmt.Fprintf(w, "\tKeep Bytes:\t%s\n", units.BytesSize(float64(rule.KeepBytes)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.FormatInGroups(n.Platforms, ngi.drivers[i].platforms), ", "))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,52 +147,24 @@ func inspectCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
options.builder = args[0]
|
options.builder = args[0]
|
||||||
}
|
}
|
||||||
return runInspect(dockerCli, options)
|
return runInspect(cmd.Context(), dockerCli, options)
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.BoolVar(&options.bootstrap, "bootstrap", false, "Ensure builder has booted before inspecting")
|
flags.BoolVar(&options.bootstrap, "bootstrap", false, "Ensure builder has booted before inspecting")
|
||||||
|
|
||||||
_ = flags
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func boot(ctx context.Context, ngi *nginfo) (bool, error) {
|
func sortedKeys(m map[string]string) []string {
|
||||||
toBoot := make([]int, 0, len(ngi.drivers))
|
s := make([]string, len(m))
|
||||||
for i, d := range ngi.drivers {
|
i := 0
|
||||||
if d.err != nil || d.di.Err != nil || d.di.Driver == nil || d.info == nil {
|
for k := range m {
|
||||||
continue
|
s[i] = k
|
||||||
}
|
i++
|
||||||
if d.info.Status != driver.Running {
|
|
||||||
toBoot = append(toBoot, i)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(toBoot) == 0 {
|
sort.Strings(s)
|
||||||
return false, nil
|
return s
|
||||||
}
|
|
||||||
|
|
||||||
pw := progress.NewPrinter(context.TODO(), os.Stderr, "auto")
|
|
||||||
|
|
||||||
mw := progress.NewMultiWriter(pw)
|
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
|
||||||
for _, idx := range toBoot {
|
|
||||||
func(idx int) {
|
|
||||||
eg.Go(func() error {
|
|
||||||
pw := mw.WithPrefix(ngi.ng.Nodes[idx].Name, len(toBoot) > 1)
|
|
||||||
_, err := driver.Boot(ctx, ngi.drivers[idx].di.Driver, pw)
|
|
||||||
if err != nil {
|
|
||||||
ngi.drivers[idx].err = err
|
|
||||||
}
|
|
||||||
close(pw.Status())
|
|
||||||
<-pw.Done()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}(idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, eg.Wait()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/config"
|
"github.com/docker/cli/cli/config"
|
||||||
@@ -45,8 +47,12 @@ func installCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runInstall(dockerCli, options)
|
return runInstall(dockerCli, options)
|
||||||
},
|
},
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide builder persistent flag for this command
|
||||||
|
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
311
commands/ls.go
311
commands/ls.go
@@ -2,73 +2,67 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"sort"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/store"
|
"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/buildx/util/platformutil"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/docker/cli/cli/command/formatter"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/sync/errgroup"
|
"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 {
|
type lsOptions struct {
|
||||||
|
format string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLs(dockerCli command.Cli, in lsOptions) error {
|
func runLs(ctx context.Context, dockerCli command.Cli, in lsOptions) error {
|
||||||
ctx := appcontext.Context()
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
|
|
||||||
txn, release, err := getStore(dockerCli)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
current, err := storeutil.GetCurrentInstance(txn, dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
builders, err := builder.GetBuilders(dockerCli, txn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
ll, err := txn.List()
|
eg, _ := errgroup.WithContext(timeoutCtx)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
builders := make([]*nginfo, len(ll))
|
|
||||||
for i, ng := range ll {
|
|
||||||
builders[i] = &nginfo{ng: ng}
|
|
||||||
}
|
|
||||||
|
|
||||||
list, err := dockerCli.ContextStore().List()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctxbuilders := make([]*nginfo, len(list))
|
|
||||||
for i, l := range list {
|
|
||||||
ctxbuilders[i] = &nginfo{ng: &store.NodeGroup{
|
|
||||||
Name: l.Name,
|
|
||||||
Nodes: []store.Node{{
|
|
||||||
Name: l.Name,
|
|
||||||
Endpoint: l.Name,
|
|
||||||
}},
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
|
|
||||||
builders = append(builders, ctxbuilders...)
|
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
|
||||||
|
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
func(b *nginfo) {
|
func(b *builder.Builder) {
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
err = loadNodeGroupData(ctx, dockerCli, b)
|
_, _ = b.LoadNodes(timeoutCtx, builder.WithData())
|
||||||
if b.err == nil && err != nil {
|
|
||||||
b.err = err
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}(b)
|
}(b)
|
||||||
@@ -78,63 +72,26 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
currentName := "default"
|
if hasErrors, err := lsPrint(dockerCli, current, builders, in.format); err != nil {
|
||||||
current, err := getCurrentInstance(txn, dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
} else if hasErrors {
|
||||||
if current != nil {
|
_, _ = fmt.Fprintf(dockerCli.Err(), "\n")
|
||||||
currentName = current.Name
|
for _, b := range builders {
|
||||||
if current.Name == "default" {
|
if b.Err() != nil {
|
||||||
currentName = current.Nodes[0].Endpoint
|
_, _ = fmt.Fprintf(dockerCli.Err(), "Cannot load builder %s: %s\n", b.Name, strings.TrimSpace(b.Err().Error()))
|
||||||
|
} else {
|
||||||
|
for _, d := range b.Nodes() {
|
||||||
|
if d.Err != nil {
|
||||||
|
_, _ = fmt.Fprintf(dockerCli.Err(), "Failed to get status for %s (%s): %s\n", b.Name, d.Name, strings.TrimSpace(d.Err.Error()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
|
||||||
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tPLATFORMS\n")
|
|
||||||
|
|
||||||
currentSet := false
|
|
||||||
for _, b := range builders {
|
|
||||||
if !currentSet && b.ng.Name == currentName {
|
|
||||||
b.ng.Name += " *"
|
|
||||||
currentSet = true
|
|
||||||
}
|
|
||||||
printngi(w, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Flush()
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printngi(w io.Writer, ngi *nginfo) {
|
|
||||||
var err string
|
|
||||||
if ngi.err != nil {
|
|
||||||
err = ngi.err.Error()
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t\n", ngi.ng.Name, ngi.ng.Driver, err)
|
|
||||||
if ngi.err == nil {
|
|
||||||
for idx, n := range ngi.ng.Nodes {
|
|
||||||
d := ngi.drivers[idx]
|
|
||||||
var err string
|
|
||||||
if d.err != nil {
|
|
||||||
err = d.err.Error()
|
|
||||||
} else if d.di.Err != nil {
|
|
||||||
err = d.di.Err.Error()
|
|
||||||
}
|
|
||||||
var status string
|
|
||||||
if d.info != nil {
|
|
||||||
status = d.info.Status.String()
|
|
||||||
}
|
|
||||||
if err != "" {
|
|
||||||
fmt.Fprintf(w, " %s\t%s\t%s\n", n.Name, n.Endpoint, err)
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, " %s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, strings.Join(platformutil.FormatInGroups(n.Platforms, d.platforms), ", "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lsCmd(dockerCli command.Cli) *cobra.Command {
|
func lsCmd(dockerCli command.Cli) *cobra.Command {
|
||||||
var options lsOptions
|
var options lsOptions
|
||||||
|
|
||||||
@@ -143,9 +100,175 @@ func lsCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
Short: "List builder instances",
|
Short: "List builder instances",
|
||||||
Args: cli.ExactArgs(0),
|
Args: cli.ExactArgs(0),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
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
|
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,22 +1,23 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/opts"
|
"github.com/docker/cli/opts"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/go-units"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/tonistiigi/units"
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -34,9 +35,7 @@ const (
|
|||||||
allCacheWarning = `WARNING! This will remove all build cache. Are you sure you want to continue?`
|
allCacheWarning = `WARNING! This will remove all build cache. Are you sure you want to continue?`
|
||||||
)
|
)
|
||||||
|
|
||||||
func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
func runPrune(ctx context.Context, dockerCli command.Cli, opts pruneOptions) error {
|
||||||
ctx := appcontext.Context()
|
|
||||||
|
|
||||||
pruneFilters := opts.filter.Value()
|
pruneFilters := opts.filter.Value()
|
||||||
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
pruneFilters = command.PruneFilters(dockerCli, pruneFilters)
|
||||||
|
|
||||||
@@ -50,18 +49,26 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
|||||||
warning = allCacheWarning
|
warning = allCacheWarning
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
if !opts.force {
|
||||||
return nil
|
if ok, err := prompt(ctx, dockerCli.In(), dockerCli.Out(), warning); err != nil {
|
||||||
|
return err
|
||||||
|
} else if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dis, err := getInstanceOrDefault(ctx, dockerCli, opts.builder, "")
|
b, err := builder.New(dockerCli, builder.WithName(opts.builder))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, di := range dis {
|
nodes, err := b.LoadNodes(ctx)
|
||||||
if di.Err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
}
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node.Err != nil {
|
||||||
|
return node.Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,11 +97,11 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
eg, ctx := errgroup.WithContext(ctx)
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
for _, di := range dis {
|
for _, node := range nodes {
|
||||||
func(di build.DriverInfo) {
|
func(node builder.Node) {
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
if di.Driver != nil {
|
if node.Driver != nil {
|
||||||
c, err := di.Driver.Client(ctx)
|
c, err := node.Driver.Client(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -109,7 +116,7 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}(di)
|
}(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
if err := eg.Wait(); err != nil {
|
||||||
@@ -119,7 +126,7 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
|||||||
<-printed
|
<-printed
|
||||||
|
|
||||||
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
|
tw = tabwriter.NewWriter(os.Stdout, 1, 8, 1, '\t', 0)
|
||||||
fmt.Fprintf(tw, "Total:\t%.2f\n", units.Bytes(total))
|
fmt.Fprintf(tw, "Total:\t%s\n", units.HumanSize(float64(total)))
|
||||||
tw.Flush()
|
tw.Flush()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -129,18 +136,18 @@ func pruneCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "prune",
|
Use: "prune",
|
||||||
Short: "Remove build cache ",
|
Short: "Remove build cache",
|
||||||
Args: cli.NoArgs,
|
Args: cli.NoArgs,
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.builder = rootOpts.builder
|
options.builder = rootOpts.builder
|
||||||
return runPrune(dockerCli, options)
|
return runPrune(cmd.Context(), dockerCli, options)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{"version": "1.00"},
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones")
|
flags.BoolVarP(&options.all, "all", "a", false, "Include internal/frontend images")
|
||||||
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=24h')")
|
flags.Var(&options.filter, "filter", `Provide filter values (e.g., "until=24h")`)
|
||||||
flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")
|
flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")
|
||||||
flags.BoolVar(&options.verbose, "verbose", false, "Provide a more verbose output")
|
flags.BoolVar(&options.verbose, "verbose", false, "Provide a more verbose output")
|
||||||
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
@@ -156,9 +163,9 @@ func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
|
|||||||
if len(untilValues) > 0 && len(unusedForValues) > 0 {
|
if len(untilValues) > 0 && len(unusedForValues) > 0 {
|
||||||
return nil, errors.Errorf("conflicting filters %q and %q", "until", "unused-for")
|
return nil, errors.Errorf("conflicting filters %q and %q", "until", "unused-for")
|
||||||
}
|
}
|
||||||
filterKey := "until"
|
untilKey := "until"
|
||||||
if len(unusedForValues) > 0 {
|
if len(unusedForValues) > 0 {
|
||||||
filterKey = "unused-for"
|
untilKey = "unused-for"
|
||||||
}
|
}
|
||||||
untilValues = append(untilValues, unusedForValues...)
|
untilValues = append(untilValues, unusedForValues...)
|
||||||
|
|
||||||
@@ -169,23 +176,27 @@ func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
|
|||||||
var err error
|
var err error
|
||||||
until, err = time.ParseDuration(untilValues[0])
|
until, err = time.ParseDuration(untilValues[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrapf(err, "%q filter expects a duration (e.g., '24h')", filterKey)
|
return nil, errors.Wrapf(err, "%q filter expects a duration (e.g., '24h')", untilKey)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("filters expect only one value")
|
return nil, errors.Errorf("filters expect only one value")
|
||||||
}
|
}
|
||||||
|
|
||||||
bkFilter := make([]string, 0, f.Len())
|
filters := make([]string, 0, f.Len())
|
||||||
for _, field := range f.Keys() {
|
for _, filterKey := range f.Keys() {
|
||||||
values := f.Get(field)
|
if filterKey == untilKey {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
values := f.Get(filterKey)
|
||||||
switch len(values) {
|
switch len(values) {
|
||||||
case 0:
|
case 0:
|
||||||
bkFilter = append(bkFilter, field)
|
filters = append(filters, filterKey)
|
||||||
case 1:
|
case 1:
|
||||||
if field == "id" {
|
if filterKey == "id" {
|
||||||
bkFilter = append(bkFilter, field+"~="+values[0])
|
filters = append(filters, filterKey+"~="+values[0])
|
||||||
} else {
|
} else {
|
||||||
bkFilter = append(bkFilter, field+"=="+values[0])
|
filters = append(filters, filterKey+"=="+values[0])
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return nil, errors.Errorf("filters expect only one value")
|
return nil, errors.Errorf("filters expect only one value")
|
||||||
@@ -193,6 +204,6 @@ func toBuildkitPruneInfo(f filters.Args) (*client.PruneInfo, error) {
|
|||||||
}
|
}
|
||||||
return &client.PruneInfo{
|
return &client.PruneInfo{
|
||||||
KeepDuration: until,
|
KeepDuration: until,
|
||||||
Filter: []string{strings.Join(bkFilter, ",")},
|
Filter: []string{strings.Join(filters, ",")},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
191
commands/rm.go
191
commands/rm.go
@@ -2,51 +2,96 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rmOptions struct {
|
type rmOptions struct {
|
||||||
builder string
|
builders []string
|
||||||
|
keepState bool
|
||||||
|
keepDaemon bool
|
||||||
|
allInactive bool
|
||||||
|
force bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runRm(dockerCli command.Cli, in rmOptions) error {
|
const (
|
||||||
ctx := appcontext.Context()
|
rmInactiveWarning = `WARNING! This will remove all builders that are not in running state. Are you sure you want to continue?`
|
||||||
|
)
|
||||||
|
|
||||||
txn, release, err := getStore(dockerCli)
|
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)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
if in.builder != "" {
|
if in.allInactive {
|
||||||
ng, err := getNodeGroup(txn, dockerCli, in.builder)
|
return rmAllInactive(ctx, txn, dockerCli, in)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err1 := stop(ctx, dockerCli, ng, true)
|
|
||||||
if err := txn.Remove(ng.Name); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ng, err := getCurrentInstance(txn, dockerCli)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
if err != nil {
|
for _, name := range in.builders {
|
||||||
return err
|
func(name string) {
|
||||||
}
|
eg.Go(func() (err error) {
|
||||||
if ng != nil {
|
defer func() {
|
||||||
err1 := stop(ctx, dockerCli, ng, true)
|
if err == nil {
|
||||||
if err := txn.Remove(ng.Name); err != nil {
|
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", name)
|
||||||
return err
|
} else {
|
||||||
}
|
_, _ = fmt.Fprintf(dockerCli.Err(), "failed to remove %s: %v\n", name, err)
|
||||||
return err1
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := eg.Wait(); err != nil {
|
||||||
|
return errors.New("failed to remove one or more builders")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,63 +99,83 @@ func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
var options rmOptions
|
var options rmOptions
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "rm [NAME]",
|
Use: "rm [OPTIONS] [NAME] [NAME...]",
|
||||||
Short: "Remove a builder instance",
|
Short: "Remove one or more builder instances",
|
||||||
Args: cli.RequiresMaxArgs(1),
|
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.builder = rootOpts.builder
|
options.builders = []string{rootOpts.builder}
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
options.builder = args[0]
|
if options.allInactive {
|
||||||
|
return errors.New("cannot specify builder name when --all-inactive is set")
|
||||||
|
}
|
||||||
|
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 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")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, rm bool) error {
|
func rm(ctx context.Context, nodes []builder.Node, in rmOptions) (err error) {
|
||||||
dis, err := driversForNodeGroup(ctx, dockerCli, ng, "")
|
for _, node := range nodes {
|
||||||
if err != nil {
|
if node.Driver == nil {
|
||||||
return err
|
continue
|
||||||
}
|
}
|
||||||
for _, di := range dis {
|
// Do not stop the buildkitd daemon when --keep-daemon is provided
|
||||||
if di.Driver != nil {
|
if !in.keepDaemon {
|
||||||
if err := di.Driver.Stop(ctx, true); err != nil {
|
if err := node.Driver.Stop(ctx, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if rm {
|
|
||||||
if err := di.Driver.Rm(ctx, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if di.Err != nil {
|
if err := node.Driver.Rm(ctx, true, !in.keepState, !in.keepDaemon); err != nil {
|
||||||
err = di.Err
|
return err
|
||||||
|
}
|
||||||
|
if node.Err != nil {
|
||||||
|
err = node.Err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopCurrent(ctx context.Context, dockerCli command.Cli, rm bool) error {
|
func rmAllInactive(ctx context.Context, txn *store.Txn, dockerCli command.Cli, in rmOptions) error {
|
||||||
dis, err := getDefaultDrivers(ctx, dockerCli, "")
|
builders, err := builder.GetBuilders(dockerCli, txn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, di := range dis {
|
|
||||||
if di.Driver != nil {
|
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
if err := di.Driver.Stop(ctx, true); err != nil {
|
defer cancel()
|
||||||
return err
|
|
||||||
}
|
eg, _ := errgroup.WithContext(timeoutCtx)
|
||||||
if rm {
|
for _, b := range builders {
|
||||||
if err := di.Driver.Rm(ctx, true); err != nil {
|
func(b *builder.Builder) {
|
||||||
return err
|
eg.Go(func() error {
|
||||||
|
nodes, err := b.LoadNodes(timeoutCtx, builder.WithData())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "cannot load %s", b.Name)
|
||||||
}
|
}
|
||||||
}
|
if b.Dynamic {
|
||||||
}
|
return nil
|
||||||
if di.Err != nil {
|
}
|
||||||
err = di.Err
|
if b.Inactive() {
|
||||||
}
|
rmerr := rm(ctx, nodes, in)
|
||||||
|
if err := txn.Remove(b.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.Name)
|
||||||
|
return rmerr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}(b)
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,24 +3,71 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
debugcmd "github.com/docker/buildx/commands/debug"
|
||||||
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
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-plugins/plugin"
|
||||||
"github.com/docker/cli/cli/command"
|
"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/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Command {
|
func NewRootCmd(name string, isPlugin bool, dockerCli command.Cli) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Short: "Build with BuildKit",
|
Short: "Docker Buildx",
|
||||||
|
Long: `Extended build capabilities with BuildKit`,
|
||||||
Use: name,
|
Use: name,
|
||||||
}
|
Annotations: map[string]string{
|
||||||
if isPlugin {
|
annotation.CodeDelimiter: `"`,
|
||||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
},
|
||||||
|
CompletionOptions: cobra.CompletionOptions{
|
||||||
|
HiddenDefaultCmd: true,
|
||||||
|
},
|
||||||
|
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
cmd.SetContext(appcontext.Context())
|
||||||
|
if !isPlugin {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return plugin.PersistentPreRunE(cmd, args)
|
return plugin.PersistentPreRunE(cmd, args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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
|
||||||
|
cmd.SilenceErrors = true
|
||||||
|
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{})
|
||||||
|
|
||||||
|
logrus.AddHook(logutil.NewFilter([]logrus.Level{
|
||||||
|
logrus.DebugLevel,
|
||||||
|
},
|
||||||
|
"serving grpc connection",
|
||||||
|
"stopping session",
|
||||||
|
"using default config store",
|
||||||
|
))
|
||||||
|
|
||||||
|
if !confutil.IsExperimental() {
|
||||||
|
cmd.SetHelpTemplate(cmd.HelpTemplate() + "\nExperimental commands and flags are hidden. Set BUILDX_EXPERIMENTAL=1 to show them.\n")
|
||||||
|
}
|
||||||
|
|
||||||
addCommands(cmd, dockerCli)
|
addCommands(cmd, dockerCli)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -34,9 +81,10 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
|||||||
rootFlags(opts, cmd.PersistentFlags())
|
rootFlags(opts, cmd.PersistentFlags())
|
||||||
|
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
buildCmd(dockerCli, opts),
|
buildCmd(dockerCli, opts, nil),
|
||||||
bakeCmd(dockerCli, opts),
|
bakeCmd(dockerCli, opts),
|
||||||
createCmd(dockerCli),
|
createCmd(dockerCli),
|
||||||
|
dialStdioCmd(dockerCli, opts),
|
||||||
rmCmd(dockerCli, opts),
|
rmCmd(dockerCli, opts),
|
||||||
lsCmd(dockerCli),
|
lsCmd(dockerCli),
|
||||||
useCmd(dockerCli, opts),
|
useCmd(dockerCli, opts),
|
||||||
@@ -47,7 +95,18 @@ func addCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
|||||||
versionCmd(dockerCli),
|
versionCmd(dockerCli),
|
||||||
pruneCmd(dockerCli, opts),
|
pruneCmd(dockerCli, opts),
|
||||||
duCmd(dockerCli, opts),
|
duCmd(dockerCli, opts),
|
||||||
imagetoolscmd.RootCmd(dockerCli),
|
imagetoolscmd.RootCmd(dockerCli, imagetoolscmd.RootOptions{Builder: &opts.builder}),
|
||||||
|
)
|
||||||
|
if confutil.IsExperimental() {
|
||||||
|
cmd.AddCommand(debugcmd.RootCmd(dockerCli,
|
||||||
|
newDebuggableBuild(dockerCli, opts),
|
||||||
|
))
|
||||||
|
remote.AddControllerCommands(cmd, dockerCli)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||||
|
"builder",
|
||||||
|
completion.BuilderNames(dockerCli),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,35 +14,20 @@ type stopOptions struct {
|
|||||||
builder string
|
builder string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runStop(dockerCli command.Cli, in stopOptions) error {
|
func runStop(ctx context.Context, dockerCli command.Cli, in stopOptions) error {
|
||||||
ctx := appcontext.Context()
|
b, err := builder.New(dockerCli,
|
||||||
|
builder.WithName(in.builder),
|
||||||
txn, release, err := getStore(dockerCli)
|
builder.WithSkippedValidation(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer release()
|
nodes, err := b.LoadNodes(ctx)
|
||||||
|
|
||||||
if in.builder != "" {
|
|
||||||
ng, err := getNodeGroup(txn, dockerCli, in.builder)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := stop(ctx, dockerCli, ng, false); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
ng, err := getCurrentInstance(txn, dockerCli)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if ng != nil {
|
|
||||||
return stop(ctx, dockerCli, ng, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return stopCurrent(ctx, dockerCli, false)
|
return stop(ctx, nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||||
@@ -54,15 +42,24 @@ func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
options.builder = args[0]
|
options.builder = args[0]
|
||||||
}
|
}
|
||||||
return runStop(dockerCli, options)
|
return runStop(cmd.Context(), dockerCli, options)
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
|
||||||
|
|
||||||
// flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, "Output destination (format: type=local,dest=path)")
|
|
||||||
|
|
||||||
_ = flags
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func stop(ctx context.Context, nodes []builder.Node) (err error) {
|
||||||
|
for _, node := range nodes {
|
||||||
|
if node.Driver != nil {
|
||||||
|
if err := node.Driver.Stop(ctx, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if node.Err != nil {
|
||||||
|
err = node.Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/config"
|
"github.com/docker/cli/cli/config"
|
||||||
@@ -51,8 +53,12 @@ func uninstallCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runUninstall(dockerCli, options)
|
return runUninstall(dockerCli, options)
|
||||||
},
|
},
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide builder persistent flag for this command
|
||||||
|
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/store/storeutil"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -16,7 +19,7 @@ type useOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runUse(dockerCli command.Cli, in useOptions) error {
|
func runUse(dockerCli command.Cli, in useOptions) error {
|
||||||
txn, release, err := getStore(dockerCli)
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -28,14 +31,11 @@ func runUse(dockerCli command.Cli, in useOptions) error {
|
|||||||
return errors.Errorf("run `docker context use default` to switch to default context")
|
return errors.Errorf("run `docker context use default` to switch to default context")
|
||||||
}
|
}
|
||||||
if in.builder == "default" || in.builder == dockerCli.CurrentContext() {
|
if in.builder == "default" || in.builder == dockerCli.CurrentContext() {
|
||||||
ep, err := getCurrentEndpoint(dockerCli)
|
ep, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := txn.SetCurrent(ep, "", false, false); err != nil {
|
return txn.SetCurrent(ep, "", false, false)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
list, err := dockerCli.ContextStore().List()
|
list, err := dockerCli.ContextStore().List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -51,15 +51,11 @@ func runUse(dockerCli command.Cli, in useOptions) error {
|
|||||||
return errors.Wrapf(err, "failed to find instance %q", in.builder)
|
return errors.Wrapf(err, "failed to find instance %q", in.builder)
|
||||||
}
|
}
|
||||||
|
|
||||||
ep, err := getCurrentEndpoint(dockerCli)
|
ep, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := txn.SetCurrent(ep, in.builder, in.isGlobal, in.isDefault); err != nil {
|
return txn.SetCurrent(ep, in.builder, in.isGlobal, in.isDefault)
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func useCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
func useCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||||
@@ -76,14 +72,12 @@ func useCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
}
|
}
|
||||||
return runUse(dockerCli, options)
|
return runUse(dockerCli, options)
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.BoolVar(&options.isGlobal, "global", false, "Builder persists context changes")
|
flags.BoolVar(&options.isGlobal, "global", false, "Builder persists context changes")
|
||||||
flags.BoolVar(&options.isDefault, "default", false, "Set builder as default for current context")
|
flags.BoolVar(&options.isDefault, "default", false, "Set builder as default for current context")
|
||||||
|
|
||||||
_ = flags
|
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
417
commands/util.go
417
commands/util.go
@@ -1,388 +1,57 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/cli/cli/streams"
|
||||||
"github.com/docker/buildx/driver"
|
|
||||||
"github.com/docker/buildx/store"
|
|
||||||
"github.com/docker/buildx/util/platformutil"
|
|
||||||
"github.com/docker/cli/cli/command"
|
|
||||||
"github.com/docker/cli/cli/context/docker"
|
|
||||||
"github.com/docker/cli/cli/context/kubernetes"
|
|
||||||
dopts "github.com/docker/cli/opts"
|
|
||||||
dockerclient "github.com/docker/docker/client"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// getStore returns current builder instance store
|
func prompt(ctx context.Context, ins io.Reader, out io.Writer, msg string) (bool, error) {
|
||||||
func getStore(dockerCli command.Cli) (*store.Txn, func(), error) {
|
done := make(chan struct{})
|
||||||
dir := filepath.Dir(dockerCli.ConfigFile().Filename)
|
var ok bool
|
||||||
s, err := store.New(dir)
|
go func() {
|
||||||
if err != nil {
|
ok = promptForConfirmation(ins, out, msg)
|
||||||
return nil, nil, err
|
close(done)
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return false, context.Cause(ctx)
|
||||||
|
case <-done:
|
||||||
|
return ok, nil
|
||||||
}
|
}
|
||||||
return s.Txn()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCurrentEndpoint returns the current default endpoint value
|
// promptForConfirmation requests and checks confirmation from user.
|
||||||
func getCurrentEndpoint(dockerCli command.Cli) (string, error) {
|
// This will display the provided message followed by ' [y/N] '. If
|
||||||
name := dockerCli.CurrentContext()
|
// the user input 'y' or 'Y' it returns true other false. If no
|
||||||
if name != "default" {
|
// message is provided "Are you sure you want to proceed? [y/N] "
|
||||||
return name, nil
|
// 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?"
|
||||||
}
|
}
|
||||||
de, err := getDockerEndpoint(dockerCli, name)
|
message += " [y/N] "
|
||||||
if err != nil {
|
|
||||||
return "", errors.Errorf("docker endpoint for %q not found", name)
|
_, _ = fmt.Fprint(outs, message)
|
||||||
|
|
||||||
|
// On Windows, force the use of the regular OS stdin stream.
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
ins = streams.NewIn(os.Stdin)
|
||||||
}
|
}
|
||||||
return de, nil
|
|
||||||
}
|
reader := bufio.NewReader(ins)
|
||||||
|
answer, _, _ := reader.ReadLine()
|
||||||
// getDockerEndpoint returns docker endpoint string for given context
|
return strings.ToLower(string(answer)) == "y"
|
||||||
func getDockerEndpoint(dockerCli command.Cli, name string) (string, error) {
|
|
||||||
list, err := dockerCli.ContextStore().List()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
for _, l := range list {
|
|
||||||
if l.Name == name {
|
|
||||||
ep, ok := l.Endpoints["docker"]
|
|
||||||
if !ok {
|
|
||||||
return "", errors.Errorf("context %q does not have a Docker endpoint", name)
|
|
||||||
}
|
|
||||||
typed, ok := ep.(docker.EndpointMeta)
|
|
||||||
if !ok {
|
|
||||||
return "", errors.Errorf("endpoint %q is not of type EndpointMeta, %T", ep, ep)
|
|
||||||
}
|
|
||||||
return typed.Host, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateEndpoint validates that endpoint is either a context or a docker host
|
|
||||||
func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
|
|
||||||
de, err := getDockerEndpoint(dockerCli, ep)
|
|
||||||
if err == nil && de != "" {
|
|
||||||
if ep == "default" {
|
|
||||||
return de, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCurrentInstance finds the current builder instance
|
|
||||||
func getCurrentInstance(txn *store.Txn, dockerCli command.Cli) (*store.NodeGroup, error) {
|
|
||||||
ep, err := getCurrentEndpoint(dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ng, err := txn.Current(ep)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ng == nil {
|
|
||||||
ng, _ = getNodeGroup(txn, dockerCli, dockerCli.CurrentContext())
|
|
||||||
}
|
|
||||||
|
|
||||||
return ng, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNodeGroup returns nodegroup based on the name
|
|
||||||
func getNodeGroup(txn *store.Txn, dockerCli command.Cli, name string) (*store.NodeGroup, error) {
|
|
||||||
ng, err := txn.NodeGroupByName(name)
|
|
||||||
if err != nil {
|
|
||||||
if !os.IsNotExist(errors.Cause(err)) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ng != nil {
|
|
||||||
return ng, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if name == "default" {
|
|
||||||
name = dockerCli.CurrentContext()
|
|
||||||
}
|
|
||||||
|
|
||||||
list, err := dockerCli.ContextStore().List()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, l := range list {
|
|
||||||
if l.Name == name {
|
|
||||||
return &store.NodeGroup{
|
|
||||||
Name: "default",
|
|
||||||
Nodes: []store.Node{
|
|
||||||
{
|
|
||||||
Name: "default",
|
|
||||||
Endpoint: name,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.Errorf("no builder %q found", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// driversForNodeGroup returns drivers for a nodegroup instance
|
|
||||||
func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, contextPathHash string) ([]build.DriverInfo, error) {
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
|
||||||
|
|
||||||
dis := make([]build.DriverInfo, len(ng.Nodes))
|
|
||||||
|
|
||||||
var f driver.Factory
|
|
||||||
if ng.Driver != "" {
|
|
||||||
f = driver.GetFactory(ng.Driver, true)
|
|
||||||
if f == nil {
|
|
||||||
return nil, errors.Errorf("failed to find driver %q", f)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dockerapi, err := clientForEndpoint(dockerCli, ng.Nodes[0].Endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f, err = driver.GetDefaultFactory(ctx, dockerapi, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ng.Driver = f.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, n := range ng.Nodes {
|
|
||||||
func(i int, n store.Node) {
|
|
||||||
eg.Go(func() error {
|
|
||||||
di := build.DriverInfo{
|
|
||||||
Name: n.Name,
|
|
||||||
Platform: n.Platforms,
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
dis[i] = di
|
|
||||||
}()
|
|
||||||
dockerapi, err := clientForEndpoint(dockerCli, n.Endpoint)
|
|
||||||
if err != nil {
|
|
||||||
di.Err = err
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// TODO: replace the following line with dockerclient.WithAPIVersionNegotiation option in clientForEndpoint
|
|
||||||
dockerapi.NegotiateAPIVersion(ctx)
|
|
||||||
|
|
||||||
contextStore := dockerCli.ContextStore()
|
|
||||||
kcc, err := kubernetes.ConfigFromContext(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: n should retain real context name.
|
|
||||||
kcc, err = kubernetes.ConfigFromContext("default", contextStore)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi, kcc, n.Flags, n.ConfigFile, n.DriverOpts, contextPathHash)
|
|
||||||
if err != nil {
|
|
||||||
di.Err = err
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
di.Driver = d
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}(i, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dis, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// clientForEndpoint returns a docker client for an endpoint
|
|
||||||
func clientForEndpoint(dockerCli command.Cli, name string) (dockerclient.APIClient, error) {
|
|
||||||
list, err := dockerCli.ContextStore().List()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for _, l := range list {
|
|
||||||
if l.Name == name {
|
|
||||||
dep, ok := l.Endpoints["docker"]
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf("context %q does not have a Docker endpoint", name)
|
|
||||||
}
|
|
||||||
epm, ok := dep.(docker.EndpointMeta)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Errorf("endpoint %q is not of type EndpointMeta, %T", dep, dep)
|
|
||||||
}
|
|
||||||
ep, err := docker.WithTLSData(dockerCli.ContextStore(), name, epm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
clientOpts, err := ep.ClientOpts()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return dockerclient.NewClientWithOpts(clientOpts...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ep := docker.Endpoint{
|
|
||||||
EndpointMeta: docker.EndpointMeta{
|
|
||||||
Host: name,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
clientOpts, err := ep.ClientOpts()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return dockerclient.NewClientWithOpts(clientOpts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getInstanceOrDefault(ctx context.Context, dockerCli command.Cli, instance, contextPathHash string) ([]build.DriverInfo, error) {
|
|
||||||
if instance != "" {
|
|
||||||
return getInstanceByName(ctx, dockerCli, instance, contextPathHash)
|
|
||||||
}
|
|
||||||
return getDefaultDrivers(ctx, dockerCli, contextPathHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getInstanceByName(ctx context.Context, dockerCli command.Cli, instance, contextPathHash string) ([]build.DriverInfo, error) {
|
|
||||||
txn, release, err := getStore(dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer release()
|
|
||||||
|
|
||||||
ng, err := txn.NodeGroupByName(instance)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return driversForNodeGroup(ctx, dockerCli, ng, contextPathHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDefaultDrivers returns drivers based on current cli config
|
|
||||||
func getDefaultDrivers(ctx context.Context, dockerCli command.Cli, contextPathHash string) ([]build.DriverInfo, error) {
|
|
||||||
txn, release, err := getStore(dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer release()
|
|
||||||
|
|
||||||
ng, err := getCurrentInstance(txn, dockerCli)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if ng != nil {
|
|
||||||
return driversForNodeGroup(ctx, dockerCli, ng, contextPathHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, dockerCli.Client(), nil, nil, "", nil, contextPathHash)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return []build.DriverInfo{
|
|
||||||
{
|
|
||||||
Name: "default",
|
|
||||||
Driver: d,
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadInfoData(ctx context.Context, d *dinfo) error {
|
|
||||||
if d.di.Driver == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
info, err := d.di.Driver.Info(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.info = info
|
|
||||||
if info.Status == driver.Running {
|
|
||||||
c, err := d.di.Driver.Client(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
workers, err := c.ListWorkers(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "listing workers")
|
|
||||||
}
|
|
||||||
for _, w := range workers {
|
|
||||||
for _, p := range w.Platforms {
|
|
||||||
d.platforms = append(d.platforms, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.platforms = platformutil.Dedupe(d.platforms)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadNodeGroupData(ctx context.Context, dockerCli command.Cli, ngi *nginfo) error {
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
|
||||||
|
|
||||||
dis, err := driversForNodeGroup(ctx, dockerCli, ngi.ng, "")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ngi.drivers = make([]dinfo, len(dis))
|
|
||||||
for i, di := range dis {
|
|
||||||
d := di
|
|
||||||
ngi.drivers[i].di = &d
|
|
||||||
func(d *dinfo) {
|
|
||||||
eg.Go(func() error {
|
|
||||||
if err := loadInfoData(ctx, d); err != nil {
|
|
||||||
d.err = err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}(&ngi.drivers[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
if eg.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, di := range ngi.drivers {
|
|
||||||
// dynamic nodes are used in Kubernetes driver.
|
|
||||||
// Kubernetes pods are dynamically mapped to BuildKit Nodes.
|
|
||||||
if di.info != nil && len(di.info.DynamicNodes) > 0 {
|
|
||||||
var drivers []dinfo
|
|
||||||
for i := 0; i < len(di.info.DynamicNodes); i++ {
|
|
||||||
// all []dinfo share *build.DriverInfo and *driver.Info
|
|
||||||
diClone := di
|
|
||||||
if pl := di.info.DynamicNodes[i].Platforms; len(pl) > 0 {
|
|
||||||
diClone.platforms = pl
|
|
||||||
}
|
|
||||||
drivers = append(drivers, di)
|
|
||||||
}
|
|
||||||
// not append (remove the static nodes in the store)
|
|
||||||
ngi.ng.Nodes = di.info.DynamicNodes
|
|
||||||
ngi.ng.Dynamic = true
|
|
||||||
ngi.drivers = drivers
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func dockerAPI(dockerCli command.Cli) *api {
|
|
||||||
return &api{dockerCli: dockerCli}
|
|
||||||
}
|
|
||||||
|
|
||||||
type api struct {
|
|
||||||
dockerCli command.Cli
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *api) DockerAPI(name string) (dockerclient.APIClient, error) {
|
|
||||||
if name == "" {
|
|
||||||
name = a.dockerCli.CurrentContext()
|
|
||||||
}
|
|
||||||
return clientForEndpoint(a.dockerCli, name)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
"github.com/docker/buildx/version"
|
"github.com/docker/buildx/version"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
@@ -17,11 +19,16 @@ func runVersion(dockerCli command.Cli) error {
|
|||||||
func versionCmd(dockerCli command.Cli) *cobra.Command {
|
func versionCmd(dockerCli command.Cli) *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "version",
|
Use: "version",
|
||||||
Short: "Show buildx version information ",
|
Short: "Show buildx version information",
|
||||||
Args: cli.ExactArgs(0),
|
Args: cli.ExactArgs(0),
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runVersion(dockerCli)
|
return runVersion(dockerCli)
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide builder persistent flag for this command
|
||||||
|
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
282
controller/build/build.go
Normal file
282
controller/build/build.go
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/build"
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
|
"github.com/docker/buildx/store"
|
||||||
|
"github.com/docker/buildx/store/storeutil"
|
||||||
|
"github.com/docker/buildx/util/buildflags"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
|
"github.com/docker/buildx/util/platformutil"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/docker/cli/cli/config"
|
||||||
|
dockeropts "github.com/docker/cli/opts"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/session/auth/authprovider"
|
||||||
|
"github.com/moby/buildkit/util/grpcerrors"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultTargetName = "default"
|
||||||
|
|
||||||
|
// RunBuild runs the specified build and returns the result.
|
||||||
|
//
|
||||||
|
// NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultHandle,
|
||||||
|
// this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can
|
||||||
|
// inspect the result and debug the cause of that error.
|
||||||
|
func RunBuild(ctx context.Context, dockerCli command.Cli, in controllerapi.BuildOptions, inStream io.Reader, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultHandle, error) {
|
||||||
|
if in.NoCache && len(in.NoCacheFilter) > 0 {
|
||||||
|
return nil, nil, errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
|
||||||
|
}
|
||||||
|
|
||||||
|
contexts := map[string]build.NamedContext{}
|
||||||
|
for name, path := range in.NamedContexts {
|
||||||
|
contexts[name] = build.NamedContext{Path: path}
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := build.Options{
|
||||||
|
Inputs: build.Inputs{
|
||||||
|
ContextPath: in.ContextPath,
|
||||||
|
DockerfilePath: in.DockerfileName,
|
||||||
|
InStream: inStream,
|
||||||
|
NamedContexts: contexts,
|
||||||
|
},
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
opts.Platforms = platforms
|
||||||
|
|
||||||
|
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
||||||
|
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(dockerConfig, nil))
|
||||||
|
|
||||||
|
secrets, err := controllerapi.CreateSecrets(in.Secrets)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
opts.Session = append(opts.Session, secrets)
|
||||||
|
|
||||||
|
sshSpecs := in.SSH
|
||||||
|
if len(sshSpecs) == 0 && buildflags.IsGitSSH(in.ContextPath) {
|
||||||
|
sshSpecs = append(sshSpecs, &controllerapi.SSH{ID: "default"})
|
||||||
|
}
|
||||||
|
ssh, err := controllerapi.CreateSSH(sshSpecs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
opts.Session = append(opts.Session, ssh)
|
||||||
|
|
||||||
|
outputs, err := controllerapi.CreateExports(in.Exports)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if in.ExportPush {
|
||||||
|
var pushUsed bool
|
||||||
|
for i := range outputs {
|
||||||
|
if outputs[i].Type == client.ExporterImage {
|
||||||
|
outputs[i].Attrs["push"] = "true"
|
||||||
|
pushUsed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !pushUsed {
|
||||||
|
outputs = append(outputs, client.ExportEntry{
|
||||||
|
Type: client.ExporterImage,
|
||||||
|
Attrs: map[string]string{
|
||||||
|
"push": "true",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.ExportLoad {
|
||||||
|
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)
|
||||||
|
opts.CacheTo = controllerapi.CreateCaches(in.CacheTo)
|
||||||
|
|
||||||
|
opts.Attests = controllerapi.CreateAttestations(in.Attests)
|
||||||
|
|
||||||
|
opts.SourcePolicy = in.SourcePolicy
|
||||||
|
|
||||||
|
allow, err := buildflags.ParseEntitlements(in.Allow)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
opts.Allow = allow
|
||||||
|
|
||||||
|
if in.PrintFunc != nil {
|
||||||
|
opts.PrintFunc = &build.PrintFunc{
|
||||||
|
Name: in.PrintFunc.Name,
|
||||||
|
Format: in.PrintFunc.Format,
|
||||||
|
IgnoreStatus: in.PrintFunc.IgnoreStatus,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// key string used for kubernetes "sticky" mode
|
||||||
|
contextPathHash, err := filepath.Abs(in.ContextPath)
|
||||||
|
if err != nil {
|
||||||
|
contextPathHash = in.ContextPath
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: this should not be loaded this side of the controller api
|
||||||
|
b, err := builder.New(dockerCli,
|
||||||
|
builder.WithName(in.Builder),
|
||||||
|
builder.WithContextPathHash(contextPathHash),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, res, err := buildTargets(ctx, dockerCli, b.NodeGroup, nodes, map[string]build.Options{defaultTargetName: opts}, progress, generateResult)
|
||||||
|
err = wrapBuildError(err, false)
|
||||||
|
if err != nil {
|
||||||
|
// NOTE: buildTargets can return *build.ResultHandle even on error.
|
||||||
|
return nil, res, err
|
||||||
|
}
|
||||||
|
return resp, res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// buildTargets runs the specified build and returns the result.
|
||||||
|
//
|
||||||
|
// NOTE: When an error happens during the build and this function acquires the debuggable *build.ResultHandle,
|
||||||
|
// this function returns it in addition to the error (i.e. it does "return nil, res, err"). The caller can
|
||||||
|
// inspect the result and debug the cause of that error.
|
||||||
|
func buildTargets(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, nodes []builder.Node, opts map[string]build.Options, progress progress.Writer, generateResult bool) (*client.SolveResponse, *build.ResultHandle, error) {
|
||||||
|
var res *build.ResultHandle
|
||||||
|
var resp map[string]*client.SolveResponse
|
||||||
|
var err error
|
||||||
|
if generateResult {
|
||||||
|
var mu sync.Mutex
|
||||||
|
var idx int
|
||||||
|
resp, err = build.BuildWithResultHandler(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress, func(driverIndex int, gotRes *build.ResultHandle) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if res == nil || driverIndex < idx {
|
||||||
|
idx, res = driverIndex, gotRes
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
resp, err = build.Build(ctx, nodes, opts, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), progress)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, res, err
|
||||||
|
}
|
||||||
|
return resp[defaultTargetName], res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapBuildError(err error, bake bool) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
st, ok := grpcerrors.AsGRPCStatus(err)
|
||||||
|
if ok {
|
||||||
|
if st.Code() == codes.Unimplemented && strings.Contains(st.Message(), "unsupported frontend capability moby.buildkit.frontend.contexts") {
|
||||||
|
msg := "current frontend does not support --build-context."
|
||||||
|
if bake {
|
||||||
|
msg = "current frontend does not support defining additional contexts for targets."
|
||||||
|
}
|
||||||
|
msg += " Named contexts are supported since Dockerfile v1.4. Use #syntax directive in Dockerfile or update to latest BuildKit."
|
||||||
|
return &wrapped{err, msg}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type wrapped struct {
|
||||||
|
err error
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapped) Error() string {
|
||||||
|
return w.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *wrapped) Unwrap() error {
|
||||||
|
return w.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateLastActivity(dockerCli command.Cli, ng *store.NodeGroup) error {
|
||||||
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
return txn.UpdateLastActivity(ng)
|
||||||
|
}
|
||||||
|
|
||||||
|
func controllerUlimitOpt2DockerUlimit(u *controllerapi.UlimitOpt) *dockeropts.UlimitOpt {
|
||||||
|
if u == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
values := make(map[string]*units.Ulimit)
|
||||||
|
for k, v := range u.Values {
|
||||||
|
values[k] = &units.Ulimit{
|
||||||
|
Name: v.Name,
|
||||||
|
Hard: v.Hard,
|
||||||
|
Soft: v.Soft,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dockeropts.NewUlimitOpt(&values)
|
||||||
|
}
|
||||||
32
controller/control/controller.go
Normal file
32
controller/control/controller.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package control
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BuildxController interface {
|
||||||
|
Build(ctx context.Context, options controllerapi.BuildOptions, in io.ReadCloser, progress progress.Writer) (ref string, resp *client.SolveResponse, err error)
|
||||||
|
// Invoke starts an IO session into the specified process.
|
||||||
|
// If pid doesn't matche to any running processes, it starts a new process with the specified config.
|
||||||
|
// If there is no container running or InvokeConfig.Rollback is speicfied, the process will start in a newly created container.
|
||||||
|
// NOTE: If needed, in the future, we can split this API into three APIs (NewContainer, NewProcess and Attach).
|
||||||
|
Invoke(ctx context.Context, ref, pid string, options controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error
|
||||||
|
Kill(ctx context.Context) error
|
||||||
|
Close() error
|
||||||
|
List(ctx context.Context) (refs []string, _ error)
|
||||||
|
Disconnect(ctx context.Context, ref string) error
|
||||||
|
ListProcesses(ctx context.Context, ref string) (infos []*controllerapi.ProcessInfo, retErr error)
|
||||||
|
DisconnectProcess(ctx context.Context, ref, pid string) error
|
||||||
|
Inspect(ctx context.Context, ref string) (*controllerapi.InspectResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ControlOptions struct {
|
||||||
|
ServerConfig string
|
||||||
|
Root string
|
||||||
|
Detach bool
|
||||||
|
}
|
||||||
36
controller/controller.go
Normal file
36
controller/controller.go
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/controller/control"
|
||||||
|
"github.com/docker/buildx/controller/local"
|
||||||
|
"github.com/docker/buildx/controller/remote"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewController(ctx context.Context, opts control.ControlOptions, dockerCli command.Cli, pw progress.Writer) (control.BuildxController, error) {
|
||||||
|
var name string
|
||||||
|
if opts.Detach {
|
||||||
|
name = "remote"
|
||||||
|
} else {
|
||||||
|
name = "local"
|
||||||
|
}
|
||||||
|
|
||||||
|
var c control.BuildxController
|
||||||
|
err := progress.Wrap(fmt.Sprintf("[internal] connecting to %s controller", name), pw.Write, func(l progress.SubLogger) (err error) {
|
||||||
|
if opts.Detach {
|
||||||
|
c, err = remote.NewRemoteBuildxController(ctx, dockerCli, opts, l)
|
||||||
|
} else {
|
||||||
|
c = local.NewLocalBuildxController(ctx, dockerCli, l)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to start buildx controller")
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
34
controller/errdefs/build.go
Normal file
34
controller/errdefs/build.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package errdefs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/containerd/typeurl/v2"
|
||||||
|
"github.com/moby/buildkit/util/grpcerrors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
typeurl.Register((*Build)(nil), "github.com/docker/buildx", "errdefs.Build+json")
|
||||||
|
}
|
||||||
|
|
||||||
|
type BuildError struct {
|
||||||
|
Build
|
||||||
|
error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BuildError) Unwrap() error {
|
||||||
|
return e.error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *BuildError) ToProto() grpcerrors.TypedErrorProto {
|
||||||
|
return &e.Build
|
||||||
|
}
|
||||||
|
|
||||||
|
func WrapBuild(err error, ref string) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &BuildError{Build: Build{Ref: ref}, error: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Build) WrapError(err error) error {
|
||||||
|
return &BuildError{error: err, Build: *b}
|
||||||
|
}
|
||||||
77
controller/errdefs/errdefs.pb.go
Normal file
77
controller/errdefs/errdefs.pb.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
// Code generated by protoc-gen-gogo. DO NOT EDIT.
|
||||||
|
// source: errdefs.proto
|
||||||
|
|
||||||
|
package errdefs
|
||||||
|
|
||||||
|
import (
|
||||||
|
fmt "fmt"
|
||||||
|
proto "github.com/gogo/protobuf/proto"
|
||||||
|
_ "github.com/moby/buildkit/solver/pb"
|
||||||
|
math "math"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal
|
||||||
|
var _ = fmt.Errorf
|
||||||
|
var _ = math.Inf
|
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||||
|
|
||||||
|
type Build struct {
|
||||||
|
Ref string `protobuf:"bytes,1,opt,name=Ref,proto3" json:"Ref,omitempty"`
|
||||||
|
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
XXX_sizecache int32 `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Build) Reset() { *m = Build{} }
|
||||||
|
func (m *Build) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Build) ProtoMessage() {}
|
||||||
|
func (*Build) Descriptor() ([]byte, []int) {
|
||||||
|
return fileDescriptor_689dc58a5060aff5, []int{0}
|
||||||
|
}
|
||||||
|
func (m *Build) XXX_Unmarshal(b []byte) error {
|
||||||
|
return xxx_messageInfo_Build.Unmarshal(m, b)
|
||||||
|
}
|
||||||
|
func (m *Build) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||||
|
return xxx_messageInfo_Build.Marshal(b, m, deterministic)
|
||||||
|
}
|
||||||
|
func (m *Build) XXX_Merge(src proto.Message) {
|
||||||
|
xxx_messageInfo_Build.Merge(m, src)
|
||||||
|
}
|
||||||
|
func (m *Build) XXX_Size() int {
|
||||||
|
return xxx_messageInfo_Build.Size(m)
|
||||||
|
}
|
||||||
|
func (m *Build) XXX_DiscardUnknown() {
|
||||||
|
xxx_messageInfo_Build.DiscardUnknown(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
var xxx_messageInfo_Build proto.InternalMessageInfo
|
||||||
|
|
||||||
|
func (m *Build) GetRef() string {
|
||||||
|
if m != nil {
|
||||||
|
return m.Ref
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*Build)(nil), "errdefs.Build")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("errdefs.proto", fileDescriptor_689dc58a5060aff5) }
|
||||||
|
|
||||||
|
var fileDescriptor_689dc58a5060aff5 = []byte{
|
||||||
|
// 111 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4d, 0x2d, 0x2a, 0x4a,
|
||||||
|
0x49, 0x4d, 0x2b, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x87, 0x72, 0xa5, 0x74, 0xd2,
|
||||||
|
0x33, 0x4b, 0x32, 0x4a, 0x93, 0xf4, 0x92, 0xf3, 0x73, 0xf5, 0x73, 0xf3, 0x93, 0x2a, 0xf5, 0x93,
|
||||||
|
0x4a, 0x33, 0x73, 0x52, 0xb2, 0x33, 0x4b, 0xf4, 0x8b, 0xf3, 0x73, 0xca, 0x52, 0x8b, 0xf4, 0x0b,
|
||||||
|
0x92, 0xf4, 0xf3, 0x0b, 0xa0, 0xda, 0x94, 0x24, 0xb9, 0x58, 0x9d, 0x40, 0xf2, 0x42, 0x02, 0x5c,
|
||||||
|
0xcc, 0x41, 0xa9, 0x69, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x20, 0x66, 0x12, 0x1b, 0x58,
|
||||||
|
0x85, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x56, 0x52, 0x41, 0x91, 0x69, 0x00, 0x00, 0x00,
|
||||||
|
}
|
||||||
9
controller/errdefs/errdefs.proto
Normal file
9
controller/errdefs/errdefs.proto
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package errdefs;
|
||||||
|
|
||||||
|
import "github.com/moby/buildkit/solver/pb/ops.proto";
|
||||||
|
|
||||||
|
message Build {
|
||||||
|
string Ref = 1;
|
||||||
|
}
|
||||||
3
controller/errdefs/generate.go
Normal file
3
controller/errdefs/generate.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package errdefs
|
||||||
|
|
||||||
|
//go:generate protoc -I=. -I=../../vendor/ --gogo_out=plugins=grpc:. errdefs.proto
|
||||||
146
controller/local/controller.go
Normal file
146
controller/local/controller.go
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package local
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/build"
|
||||||
|
cbuild "github.com/docker/buildx/controller/build"
|
||||||
|
"github.com/docker/buildx/controller/control"
|
||||||
|
controllererrors "github.com/docker/buildx/controller/errdefs"
|
||||||
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
|
"github.com/docker/buildx/controller/processes"
|
||||||
|
"github.com/docker/buildx/util/ioset"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewLocalBuildxController(ctx context.Context, dockerCli command.Cli, logger progress.SubLogger) control.BuildxController {
|
||||||
|
return &localController{
|
||||||
|
dockerCli: dockerCli,
|
||||||
|
ref: "local",
|
||||||
|
processes: processes.NewManager(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type buildConfig struct {
|
||||||
|
// TODO: these two structs should be merged
|
||||||
|
// Discussion: https://github.com/docker/buildx/pull/1640#discussion_r1113279719
|
||||||
|
resultCtx *build.ResultHandle
|
||||||
|
buildOptions *controllerapi.BuildOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
type localController struct {
|
||||||
|
dockerCli command.Cli
|
||||||
|
ref string
|
||||||
|
buildConfig buildConfig
|
||||||
|
processes *processes.Manager
|
||||||
|
|
||||||
|
buildOnGoing atomic.Bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *localController) Build(ctx context.Context, options controllerapi.BuildOptions, in io.ReadCloser, progress progress.Writer) (string, *client.SolveResponse, error) {
|
||||||
|
if !b.buildOnGoing.CompareAndSwap(false, true) {
|
||||||
|
return "", nil, errors.New("build ongoing")
|
||||||
|
}
|
||||||
|
defer b.buildOnGoing.Store(false)
|
||||||
|
|
||||||
|
resp, res, buildErr := cbuild.RunBuild(ctx, b.dockerCli, options, in, progress, true)
|
||||||
|
// NOTE: RunBuild can return *build.ResultHandle even on error.
|
||||||
|
if res != nil {
|
||||||
|
b.buildConfig = buildConfig{
|
||||||
|
resultCtx: res,
|
||||||
|
buildOptions: &options,
|
||||||
|
}
|
||||||
|
if buildErr != nil {
|
||||||
|
buildErr = controllererrors.WrapBuild(buildErr, b.ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if buildErr != nil {
|
||||||
|
return "", nil, buildErr
|
||||||
|
}
|
||||||
|
return b.ref, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *localController) ListProcesses(ctx context.Context, ref string) (infos []*controllerapi.ProcessInfo, retErr error) {
|
||||||
|
if ref != b.ref {
|
||||||
|
return nil, errors.Errorf("unknown ref %q", ref)
|
||||||
|
}
|
||||||
|
return b.processes.ListProcesses(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *localController) DisconnectProcess(ctx context.Context, ref, pid string) error {
|
||||||
|
if ref != b.ref {
|
||||||
|
return errors.Errorf("unknown ref %q", ref)
|
||||||
|
}
|
||||||
|
return b.processes.DeleteProcess(pid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *localController) cancelRunningProcesses() {
|
||||||
|
b.processes.CancelRunningProcesses()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *localController) Invoke(ctx context.Context, ref string, pid string, cfg controllerapi.InvokeConfig, ioIn io.ReadCloser, ioOut io.WriteCloser, ioErr io.WriteCloser) error {
|
||||||
|
if ref != b.ref {
|
||||||
|
return errors.Errorf("unknown ref %q", ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
proc, ok := b.processes.Get(pid)
|
||||||
|
if !ok {
|
||||||
|
// Start a new process.
|
||||||
|
if b.buildConfig.resultCtx == nil {
|
||||||
|
return errors.New("no build result is registered")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
proc, err = b.processes.StartProcess(pid, b.buildConfig.resultCtx, &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach containerIn to this process
|
||||||
|
ioCancelledCh := make(chan struct{})
|
||||||
|
proc.ForwardIO(&ioset.In{Stdin: ioIn, Stdout: ioOut, Stderr: ioErr}, func() { close(ioCancelledCh) })
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ioCancelledCh:
|
||||||
|
return errors.Errorf("io cancelled")
|
||||||
|
case err := <-proc.Done():
|
||||||
|
return err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *localController) Kill(context.Context) error {
|
||||||
|
b.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *localController) Close() error {
|
||||||
|
b.cancelRunningProcesses()
|
||||||
|
if b.buildConfig.resultCtx != nil {
|
||||||
|
b.buildConfig.resultCtx.Done()
|
||||||
|
}
|
||||||
|
// TODO: cancel ongoing builds?
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *localController) List(ctx context.Context) (res []string, _ error) {
|
||||||
|
return []string{b.ref}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *localController) Disconnect(ctx context.Context, key string) error {
|
||||||
|
b.Close()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *localController) Inspect(ctx context.Context, ref string) (*controllerapi.InspectResponse, error) {
|
||||||
|
if ref != b.ref {
|
||||||
|
return nil, errors.Errorf("unknown ref %q", ref)
|
||||||
|
}
|
||||||
|
return &controllerapi.InspectResponse{Options: b.buildConfig.buildOptions}, nil
|
||||||
|
}
|
||||||
20
controller/pb/attest.go
Normal file
20
controller/pb/attest.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package pb
|
||||||
|
|
||||||
|
func CreateAttestations(attests []*Attest) map[string]*string {
|
||||||
|
result := map[string]*string{}
|
||||||
|
for _, attest := range attests {
|
||||||
|
// ignore duplicates
|
||||||
|
if _, ok := result[attest.Type]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if attest.Disabled {
|
||||||
|
result[attest.Type] = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := attest.Attrs
|
||||||
|
result[attest.Type] = &attrs
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
21
controller/pb/cache.go
Normal file
21
controller/pb/cache.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package pb
|
||||||
|
|
||||||
|
import "github.com/moby/buildkit/client"
|
||||||
|
|
||||||
|
func CreateCaches(entries []*CacheOptionsEntry) []client.CacheOptionsEntry {
|
||||||
|
var outs []client.CacheOptionsEntry
|
||||||
|
if len(entries) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
out := client.CacheOptionsEntry{
|
||||||
|
Type: entry.Type,
|
||||||
|
Attrs: map[string]string{},
|
||||||
|
}
|
||||||
|
for k, v := range entry.Attrs {
|
||||||
|
out.Attrs[k] = v
|
||||||
|
}
|
||||||
|
outs = append(outs, out)
|
||||||
|
}
|
||||||
|
return outs
|
||||||
|
}
|
||||||
2719
controller/pb/controller.pb.go
Normal file
2719
controller/pb/controller.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
250
controller/pb/controller.proto
Normal file
250
controller/pb/controller.proto
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package buildx.controller.v1;
|
||||||
|
|
||||||
|
import "github.com/moby/buildkit/api/services/control/control.proto";
|
||||||
|
import "github.com/moby/buildkit/sourcepolicy/pb/policy.proto";
|
||||||
|
|
||||||
|
option go_package = "pb";
|
||||||
|
|
||||||
|
service Controller {
|
||||||
|
rpc Build(BuildRequest) returns (BuildResponse);
|
||||||
|
rpc Inspect(InspectRequest) returns (InspectResponse);
|
||||||
|
rpc Status(StatusRequest) returns (stream StatusResponse);
|
||||||
|
rpc Input(stream InputMessage) returns (InputResponse);
|
||||||
|
rpc Invoke(stream Message) returns (stream Message);
|
||||||
|
rpc List(ListRequest) returns (ListResponse);
|
||||||
|
rpc Disconnect(DisconnectRequest) returns (DisconnectResponse);
|
||||||
|
rpc Info(InfoRequest) returns (InfoResponse);
|
||||||
|
rpc ListProcesses(ListProcessesRequest) returns (ListProcessesResponse);
|
||||||
|
rpc DisconnectProcess(DisconnectProcessRequest) returns (DisconnectProcessResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListProcessesRequest {
|
||||||
|
string Ref = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListProcessesResponse {
|
||||||
|
repeated ProcessInfo Infos = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ProcessInfo {
|
||||||
|
string ProcessID = 1;
|
||||||
|
InvokeConfig InvokeConfig = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DisconnectProcessRequest {
|
||||||
|
string Ref = 1;
|
||||||
|
string ProcessID = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DisconnectProcessResponse {
|
||||||
|
}
|
||||||
|
|
||||||
|
message BuildRequest {
|
||||||
|
string Ref = 1;
|
||||||
|
BuildOptions Options = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BuildOptions {
|
||||||
|
string ContextPath = 1;
|
||||||
|
string DockerfileName = 2;
|
||||||
|
PrintFunc PrintFunc = 3;
|
||||||
|
map<string, string> NamedContexts = 4;
|
||||||
|
|
||||||
|
repeated string Allow = 5;
|
||||||
|
repeated Attest Attests = 6;
|
||||||
|
map<string, string> BuildArgs = 7;
|
||||||
|
repeated CacheOptionsEntry CacheFrom = 8;
|
||||||
|
repeated CacheOptionsEntry CacheTo = 9;
|
||||||
|
string CgroupParent = 10;
|
||||||
|
repeated ExportEntry Exports = 11;
|
||||||
|
repeated string ExtraHosts = 12;
|
||||||
|
map<string, string> Labels = 13;
|
||||||
|
string NetworkMode = 14;
|
||||||
|
repeated string NoCacheFilter = 15;
|
||||||
|
repeated string Platforms = 16;
|
||||||
|
repeated Secret Secrets = 17;
|
||||||
|
int64 ShmSize = 18;
|
||||||
|
repeated SSH SSH = 19;
|
||||||
|
repeated string Tags = 20;
|
||||||
|
string Target = 21;
|
||||||
|
UlimitOpt Ulimits = 22;
|
||||||
|
|
||||||
|
string Builder = 23;
|
||||||
|
bool NoCache = 24;
|
||||||
|
bool Pull = 25;
|
||||||
|
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 {
|
||||||
|
string Type = 1;
|
||||||
|
map<string, string> Attrs = 2;
|
||||||
|
string Destination = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CacheOptionsEntry {
|
||||||
|
string Type = 1;
|
||||||
|
map<string, string> Attrs = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Attest {
|
||||||
|
string Type = 1;
|
||||||
|
bool Disabled = 2;
|
||||||
|
string Attrs = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SSH {
|
||||||
|
string ID = 1;
|
||||||
|
repeated string Paths = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Secret {
|
||||||
|
string ID = 1;
|
||||||
|
string FilePath = 2;
|
||||||
|
string Env = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message PrintFunc {
|
||||||
|
string Name = 1;
|
||||||
|
string Format = 2;
|
||||||
|
bool IgnoreStatus = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InspectRequest {
|
||||||
|
string Ref = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InspectResponse {
|
||||||
|
BuildOptions Options = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message UlimitOpt {
|
||||||
|
map<string, Ulimit> values = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Ulimit {
|
||||||
|
string Name = 1;
|
||||||
|
int64 Hard = 2;
|
||||||
|
int64 Soft = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BuildResponse {
|
||||||
|
map<string, string> ExporterResponse = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DisconnectRequest {
|
||||||
|
string Ref = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DisconnectResponse {}
|
||||||
|
|
||||||
|
message ListRequest {
|
||||||
|
string Ref = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListResponse {
|
||||||
|
repeated string keys = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InputMessage {
|
||||||
|
oneof Input {
|
||||||
|
InputInitMessage Init = 1;
|
||||||
|
DataMessage Data = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message InputInitMessage {
|
||||||
|
string Ref = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DataMessage {
|
||||||
|
bool EOF = 1; // true if eof was reached
|
||||||
|
bytes Data = 2; // should be chunked smaller than 4MB:
|
||||||
|
// https://pkg.go.dev/google.golang.org/grpc#MaxRecvMsgSize
|
||||||
|
}
|
||||||
|
|
||||||
|
message InputResponse {}
|
||||||
|
|
||||||
|
message Message {
|
||||||
|
oneof Input {
|
||||||
|
InitMessage Init = 1;
|
||||||
|
// FdMessage used from client to server for input (stdin) and
|
||||||
|
// from server to client for output (stdout, stderr)
|
||||||
|
FdMessage File = 2;
|
||||||
|
// ResizeMessage used from client to server for terminal resize events
|
||||||
|
ResizeMessage Resize = 3;
|
||||||
|
// SignalMessage is used from client to server to send signal events
|
||||||
|
SignalMessage Signal = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message InitMessage {
|
||||||
|
string Ref = 1;
|
||||||
|
|
||||||
|
// If ProcessID already exists in the server, it tries to connect to it
|
||||||
|
// instead of invoking the new one. In this case, InvokeConfig will be ignored.
|
||||||
|
string ProcessID = 2;
|
||||||
|
InvokeConfig InvokeConfig = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
string Cwd = 6;
|
||||||
|
bool NoCwd = 7; // Do not set cwd but use the image's default
|
||||||
|
bool Tty = 8;
|
||||||
|
bool Rollback = 9; // Kill all process in the container and recreate it.
|
||||||
|
bool Initial = 10; // Run container from the initial state of that stage (supported only on the failed step)
|
||||||
|
}
|
||||||
|
|
||||||
|
message FdMessage {
|
||||||
|
uint32 Fd = 1; // what fd the data was from
|
||||||
|
bool EOF = 2; // true if eof was reached
|
||||||
|
bytes Data = 3; // should be chunked smaller than 4MB:
|
||||||
|
// https://pkg.go.dev/google.golang.org/grpc#MaxRecvMsgSize
|
||||||
|
}
|
||||||
|
|
||||||
|
message ResizeMessage {
|
||||||
|
uint32 Rows = 1;
|
||||||
|
uint32 Cols = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SignalMessage {
|
||||||
|
// we only send name (ie HUP, INT) because the int values
|
||||||
|
// are platform dependent.
|
||||||
|
string Name = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StatusRequest {
|
||||||
|
string Ref = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StatusResponse {
|
||||||
|
repeated moby.buildkit.v1.Vertex vertexes = 1;
|
||||||
|
repeated moby.buildkit.v1.VertexStatus statuses = 2;
|
||||||
|
repeated moby.buildkit.v1.VertexLog logs = 3;
|
||||||
|
repeated moby.buildkit.v1.VertexWarning warnings = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message InfoRequest {}
|
||||||
|
|
||||||
|
message InfoResponse {
|
||||||
|
BuildxVersion buildxVersion = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BuildxVersion {
|
||||||
|
string package = 1;
|
||||||
|
string version = 2;
|
||||||
|
string revision = 3;
|
||||||
|
}
|
||||||
105
controller/pb/export.go
Normal file
105
controller/pb/export.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/containerd/console"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateExports(entries []*ExportEntry) ([]client.ExportEntry, error) {
|
||||||
|
var outs []client.ExportEntry
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
out := client.ExportEntry{
|
||||||
|
Type: entry.Type,
|
||||||
|
Attrs: map[string]string{},
|
||||||
|
}
|
||||||
|
for k, v := range entry.Attrs {
|
||||||
|
out.Attrs[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
supportFile := false
|
||||||
|
supportDir := false
|
||||||
|
switch out.Type {
|
||||||
|
case client.ExporterLocal:
|
||||||
|
supportDir = true
|
||||||
|
case client.ExporterTar:
|
||||||
|
supportFile = true
|
||||||
|
case client.ExporterOCI, client.ExporterDocker:
|
||||||
|
tar, err := strconv.ParseBool(out.Attrs["tar"])
|
||||||
|
if err != nil {
|
||||||
|
tar = true
|
||||||
|
}
|
||||||
|
supportFile = tar
|
||||||
|
supportDir = !tar
|
||||||
|
case "registry":
|
||||||
|
out.Type = client.ExporterImage
|
||||||
|
}
|
||||||
|
|
||||||
|
if supportDir {
|
||||||
|
if entry.Destination == "" {
|
||||||
|
return nil, errors.Errorf("dest is required for %s exporter", out.Type)
|
||||||
|
}
|
||||||
|
if entry.Destination == "-" {
|
||||||
|
return nil, errors.Errorf("dest cannot be stdout for %s exporter", out.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
fi, err := os.Stat(entry.Destination)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return nil, errors.Wrapf(err, "invalid destination directory: %s", entry.Destination)
|
||||||
|
}
|
||||||
|
if err == nil && !fi.IsDir() {
|
||||||
|
return nil, errors.Errorf("destination directory %s is a file", entry.Destination)
|
||||||
|
}
|
||||||
|
out.OutputDir = entry.Destination
|
||||||
|
}
|
||||||
|
if supportFile {
|
||||||
|
if entry.Destination == "" && out.Type != client.ExporterDocker {
|
||||||
|
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) {
|
||||||
|
return nil, errors.Wrapf(err, "invalid destination file: %s", entry.Destination)
|
||||||
|
}
|
||||||
|
if err == nil && fi.IsDir() {
|
||||||
|
return nil, errors.Errorf("destination file %s is a directory", entry.Destination)
|
||||||
|
}
|
||||||
|
f, err := os.Create(entry.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("failed to open %s", err)
|
||||||
|
}
|
||||||
|
out.Output = wrapWriteCloser(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outs = append(outs, out)
|
||||||
|
}
|
||||||
|
return outs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapWriteCloser(wc io.WriteCloser) func(map[string]string) (io.WriteCloser, error) {
|
||||||
|
return func(map[string]string) (io.WriteCloser, error) {
|
||||||
|
return wc, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
3
controller/pb/generate.go
Normal file
3
controller/pb/generate.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package pb
|
||||||
|
|
||||||
|
//go:generate protoc -I=. -I=../../vendor/ --gogo_out=plugins=grpc:. controller.proto
|
||||||
175
controller/pb/path.go
Normal file
175
controller/pb/path.go
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||||
|
"github.com/moby/buildkit/util/gitutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ResolveOptionPaths resolves all paths contained in BuildOptions
|
||||||
|
// and replaces them to absolute paths.
|
||||||
|
func ResolveOptionPaths(options *BuildOptions) (_ *BuildOptions, err error) {
|
||||||
|
localContext := false
|
||||||
|
if options.ContextPath != "" && options.ContextPath != "-" {
|
||||||
|
if !isRemoteURL(options.ContextPath) {
|
||||||
|
localContext = true
|
||||||
|
options.ContextPath, err = filepath.Abs(options.ContextPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if options.DockerfileName != "" && options.DockerfileName != "-" {
|
||||||
|
if localContext && !urlutil.IsURL(options.DockerfileName) {
|
||||||
|
options.DockerfileName, err = filepath.Abs(options.DockerfileName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var contexts map[string]string
|
||||||
|
for k, v := range options.NamedContexts {
|
||||||
|
if isRemoteURL(v) || strings.HasPrefix(v, "docker-image://") {
|
||||||
|
// url prefix, this is a remote path
|
||||||
|
} else if strings.HasPrefix(v, "oci-layout://") {
|
||||||
|
// oci layout prefix, this is a local path
|
||||||
|
p := strings.TrimPrefix(v, "oci-layout://")
|
||||||
|
p, err = filepath.Abs(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v = "oci-layout://" + p
|
||||||
|
} else {
|
||||||
|
// no prefix, assume local path
|
||||||
|
v, err = filepath.Abs(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if contexts == nil {
|
||||||
|
contexts = make(map[string]string)
|
||||||
|
}
|
||||||
|
contexts[k] = v
|
||||||
|
}
|
||||||
|
options.NamedContexts = contexts
|
||||||
|
|
||||||
|
var cacheFrom []*CacheOptionsEntry
|
||||||
|
for _, co := range options.CacheFrom {
|
||||||
|
switch co.Type {
|
||||||
|
case "local":
|
||||||
|
var attrs map[string]string
|
||||||
|
for k, v := range co.Attrs {
|
||||||
|
if attrs == nil {
|
||||||
|
attrs = make(map[string]string)
|
||||||
|
}
|
||||||
|
switch k {
|
||||||
|
case "src":
|
||||||
|
p := v
|
||||||
|
if p != "" {
|
||||||
|
p, err = filepath.Abs(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attrs[k] = p
|
||||||
|
default:
|
||||||
|
attrs[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
co.Attrs = attrs
|
||||||
|
cacheFrom = append(cacheFrom, co)
|
||||||
|
default:
|
||||||
|
cacheFrom = append(cacheFrom, co)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options.CacheFrom = cacheFrom
|
||||||
|
|
||||||
|
var cacheTo []*CacheOptionsEntry
|
||||||
|
for _, co := range options.CacheTo {
|
||||||
|
switch co.Type {
|
||||||
|
case "local":
|
||||||
|
var attrs map[string]string
|
||||||
|
for k, v := range co.Attrs {
|
||||||
|
if attrs == nil {
|
||||||
|
attrs = make(map[string]string)
|
||||||
|
}
|
||||||
|
switch k {
|
||||||
|
case "dest":
|
||||||
|
p := v
|
||||||
|
if p != "" {
|
||||||
|
p, err = filepath.Abs(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attrs[k] = p
|
||||||
|
default:
|
||||||
|
attrs[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
co.Attrs = attrs
|
||||||
|
cacheTo = append(cacheTo, co)
|
||||||
|
default:
|
||||||
|
cacheTo = append(cacheTo, co)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options.CacheTo = cacheTo
|
||||||
|
var exports []*ExportEntry
|
||||||
|
for _, e := range options.Exports {
|
||||||
|
if e.Destination != "" && e.Destination != "-" {
|
||||||
|
e.Destination, err = filepath.Abs(e.Destination)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports = append(exports, e)
|
||||||
|
}
|
||||||
|
options.Exports = exports
|
||||||
|
|
||||||
|
var secrets []*Secret
|
||||||
|
for _, s := range options.Secrets {
|
||||||
|
if s.FilePath != "" {
|
||||||
|
s.FilePath, err = filepath.Abs(s.FilePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
secrets = append(secrets, s)
|
||||||
|
}
|
||||||
|
options.Secrets = secrets
|
||||||
|
|
||||||
|
var ssh []*SSH
|
||||||
|
for _, s := range options.SSH {
|
||||||
|
var ps []string
|
||||||
|
for _, pt := range s.Paths {
|
||||||
|
p := pt
|
||||||
|
if p != "" {
|
||||||
|
p, err = filepath.Abs(p)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ps = append(ps, p)
|
||||||
|
|
||||||
|
}
|
||||||
|
s.Paths = ps
|
||||||
|
ssh = append(ssh, s)
|
||||||
|
}
|
||||||
|
options.SSH = ssh
|
||||||
|
|
||||||
|
return options, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isRemoteURL(c string) bool {
|
||||||
|
if urlutil.IsURL(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, err := gitutil.ParseGitRef(c); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
248
controller/pb/path_test.go
Normal file
248
controller/pb/path_test.go
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolvePaths(t *testing.T) {
|
||||||
|
tmpwd, err := os.MkdirTemp("", "testresolvepaths")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.Remove(tmpwd)
|
||||||
|
require.NoError(t, os.Chdir(tmpwd))
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
options BuildOptions
|
||||||
|
want BuildOptions
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "contextpath",
|
||||||
|
options: BuildOptions{ContextPath: "test"},
|
||||||
|
want: BuildOptions{ContextPath: filepath.Join(tmpwd, "test")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contextpath-cwd",
|
||||||
|
options: BuildOptions{ContextPath: "."},
|
||||||
|
want: BuildOptions{ContextPath: tmpwd},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contextpath-dash",
|
||||||
|
options: BuildOptions{ContextPath: "-"},
|
||||||
|
want: BuildOptions{ContextPath: "-"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contextpath-ssh",
|
||||||
|
options: BuildOptions{ContextPath: "git@github.com:docker/buildx.git"},
|
||||||
|
want: BuildOptions{ContextPath: "git@github.com:docker/buildx.git"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dockerfilename",
|
||||||
|
options: BuildOptions{DockerfileName: "test", ContextPath: "."},
|
||||||
|
want: BuildOptions{DockerfileName: filepath.Join(tmpwd, "test"), ContextPath: tmpwd},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dockerfilename-dash",
|
||||||
|
options: BuildOptions{DockerfileName: "-", ContextPath: "."},
|
||||||
|
want: BuildOptions{DockerfileName: "-", ContextPath: tmpwd},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dockerfilename-remote",
|
||||||
|
options: BuildOptions{DockerfileName: "test", ContextPath: "git@github.com:docker/buildx.git"},
|
||||||
|
want: BuildOptions{DockerfileName: "test", ContextPath: "git@github.com:docker/buildx.git"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contexts",
|
||||||
|
options: BuildOptions{NamedContexts: map[string]string{"a": "test1", "b": "test2",
|
||||||
|
"alpine": "docker-image://alpine@sha256:0123456789", "project": "https://github.com/myuser/project.git"}},
|
||||||
|
want: BuildOptions{NamedContexts: map[string]string{"a": filepath.Join(tmpwd, "test1"), "b": filepath.Join(tmpwd, "test2"),
|
||||||
|
"alpine": "docker-image://alpine@sha256:0123456789", "project": "https://github.com/myuser/project.git"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cache-from",
|
||||||
|
options: BuildOptions{
|
||||||
|
CacheFrom: []*CacheOptionsEntry{
|
||||||
|
{
|
||||||
|
Type: "local",
|
||||||
|
Attrs: map[string]string{"src": "test"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "registry",
|
||||||
|
Attrs: map[string]string{"ref": "user/app"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: BuildOptions{
|
||||||
|
CacheFrom: []*CacheOptionsEntry{
|
||||||
|
{
|
||||||
|
Type: "local",
|
||||||
|
Attrs: map[string]string{"src": filepath.Join(tmpwd, "test")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "registry",
|
||||||
|
Attrs: map[string]string{"ref": "user/app"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "cache-to",
|
||||||
|
options: BuildOptions{
|
||||||
|
CacheTo: []*CacheOptionsEntry{
|
||||||
|
{
|
||||||
|
Type: "local",
|
||||||
|
Attrs: map[string]string{"dest": "test"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "registry",
|
||||||
|
Attrs: map[string]string{"ref": "user/app"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: BuildOptions{
|
||||||
|
CacheTo: []*CacheOptionsEntry{
|
||||||
|
{
|
||||||
|
Type: "local",
|
||||||
|
Attrs: map[string]string{"dest": filepath.Join(tmpwd, "test")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "registry",
|
||||||
|
Attrs: map[string]string{"ref": "user/app"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "exports",
|
||||||
|
options: BuildOptions{
|
||||||
|
Exports: []*ExportEntry{
|
||||||
|
{
|
||||||
|
Type: "local",
|
||||||
|
Destination: "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "local",
|
||||||
|
Destination: "test1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "tar",
|
||||||
|
Destination: "test3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "oci",
|
||||||
|
Destination: "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "docker",
|
||||||
|
Destination: "test4",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "image",
|
||||||
|
Attrs: map[string]string{"push": "true"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: BuildOptions{
|
||||||
|
Exports: []*ExportEntry{
|
||||||
|
{
|
||||||
|
Type: "local",
|
||||||
|
Destination: "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "local",
|
||||||
|
Destination: filepath.Join(tmpwd, "test1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "tar",
|
||||||
|
Destination: filepath.Join(tmpwd, "test3"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "oci",
|
||||||
|
Destination: "-",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "docker",
|
||||||
|
Destination: filepath.Join(tmpwd, "test4"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: "image",
|
||||||
|
Attrs: map[string]string{"push": "true"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "secrets",
|
||||||
|
options: BuildOptions{
|
||||||
|
Secrets: []*Secret{
|
||||||
|
{
|
||||||
|
FilePath: "test1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "val",
|
||||||
|
Env: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "test",
|
||||||
|
FilePath: "test3",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: BuildOptions{
|
||||||
|
Secrets: []*Secret{
|
||||||
|
{
|
||||||
|
FilePath: filepath.Join(tmpwd, "test1"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "val",
|
||||||
|
Env: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "test",
|
||||||
|
FilePath: filepath.Join(tmpwd, "test3"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ssh",
|
||||||
|
options: BuildOptions{
|
||||||
|
SSH: []*SSH{
|
||||||
|
{
|
||||||
|
ID: "default",
|
||||||
|
Paths: []string{"test1", "test2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
Paths: []string{"test3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: BuildOptions{
|
||||||
|
SSH: []*SSH{
|
||||||
|
{
|
||||||
|
ID: "default",
|
||||||
|
Paths: []string{filepath.Join(tmpwd, "test1"), filepath.Join(tmpwd, "test2")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
Paths: []string{filepath.Join(tmpwd, "test3")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := ResolveOptionPaths(&tt.options)
|
||||||
|
require.NoError(t, err)
|
||||||
|
if !reflect.DeepEqual(tt.want, *got) {
|
||||||
|
t.Fatalf("expected %#v, got %#v", tt.want, *got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
126
controller/pb/progress.go
Normal file
126
controller/pb/progress.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
control "github.com/moby/buildkit/api/services/control"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/opencontainers/go-digest"
|
||||||
|
)
|
||||||
|
|
||||||
|
type writer struct {
|
||||||
|
ch chan<- *StatusResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProgressWriter(ch chan<- *StatusResponse) progress.Writer {
|
||||||
|
return &writer{ch: ch}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writer) Write(status *client.SolveStatus) {
|
||||||
|
w.ch <- ToControlStatus(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writer) WriteBuildRef(target string, ref string) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writer) ValidateLogSource(digest.Digest, interface{}) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *writer) ClearLogSource(interface{}) {}
|
||||||
|
|
||||||
|
func ToControlStatus(s *client.SolveStatus) *StatusResponse {
|
||||||
|
resp := StatusResponse{}
|
||||||
|
for _, v := range s.Vertexes {
|
||||||
|
resp.Vertexes = append(resp.Vertexes, &control.Vertex{
|
||||||
|
Digest: v.Digest,
|
||||||
|
Inputs: v.Inputs,
|
||||||
|
Name: v.Name,
|
||||||
|
Started: v.Started,
|
||||||
|
Completed: v.Completed,
|
||||||
|
Error: v.Error,
|
||||||
|
Cached: v.Cached,
|
||||||
|
ProgressGroup: v.ProgressGroup,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, v := range s.Statuses {
|
||||||
|
resp.Statuses = append(resp.Statuses, &control.VertexStatus{
|
||||||
|
ID: v.ID,
|
||||||
|
Vertex: v.Vertex,
|
||||||
|
Name: v.Name,
|
||||||
|
Total: v.Total,
|
||||||
|
Current: v.Current,
|
||||||
|
Timestamp: v.Timestamp,
|
||||||
|
Started: v.Started,
|
||||||
|
Completed: v.Completed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, v := range s.Logs {
|
||||||
|
resp.Logs = append(resp.Logs, &control.VertexLog{
|
||||||
|
Vertex: v.Vertex,
|
||||||
|
Stream: int64(v.Stream),
|
||||||
|
Msg: v.Data,
|
||||||
|
Timestamp: v.Timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, v := range s.Warnings {
|
||||||
|
resp.Warnings = append(resp.Warnings, &control.VertexWarning{
|
||||||
|
Vertex: v.Vertex,
|
||||||
|
Level: int64(v.Level),
|
||||||
|
Short: v.Short,
|
||||||
|
Detail: v.Detail,
|
||||||
|
Url: v.URL,
|
||||||
|
Info: v.SourceInfo,
|
||||||
|
Ranges: v.Range,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &resp
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromControlStatus(resp *StatusResponse) *client.SolveStatus {
|
||||||
|
s := client.SolveStatus{}
|
||||||
|
for _, v := range resp.Vertexes {
|
||||||
|
s.Vertexes = append(s.Vertexes, &client.Vertex{
|
||||||
|
Digest: v.Digest,
|
||||||
|
Inputs: v.Inputs,
|
||||||
|
Name: v.Name,
|
||||||
|
Started: v.Started,
|
||||||
|
Completed: v.Completed,
|
||||||
|
Error: v.Error,
|
||||||
|
Cached: v.Cached,
|
||||||
|
ProgressGroup: v.ProgressGroup,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, v := range resp.Statuses {
|
||||||
|
s.Statuses = append(s.Statuses, &client.VertexStatus{
|
||||||
|
ID: v.ID,
|
||||||
|
Vertex: v.Vertex,
|
||||||
|
Name: v.Name,
|
||||||
|
Total: v.Total,
|
||||||
|
Current: v.Current,
|
||||||
|
Timestamp: v.Timestamp,
|
||||||
|
Started: v.Started,
|
||||||
|
Completed: v.Completed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, v := range resp.Logs {
|
||||||
|
s.Logs = append(s.Logs, &client.VertexLog{
|
||||||
|
Vertex: v.Vertex,
|
||||||
|
Stream: int(v.Stream),
|
||||||
|
Data: v.Msg,
|
||||||
|
Timestamp: v.Timestamp,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, v := range resp.Warnings {
|
||||||
|
s.Warnings = append(s.Warnings, &client.VertexWarning{
|
||||||
|
Vertex: v.Vertex,
|
||||||
|
Level: int(v.Level),
|
||||||
|
Short: v.Short,
|
||||||
|
Detail: v.Detail,
|
||||||
|
URL: v.Url,
|
||||||
|
SourceInfo: v.Info,
|
||||||
|
Range: v.Ranges,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return &s
|
||||||
|
}
|
||||||
22
controller/pb/secrets.go
Normal file
22
controller/pb/secrets.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/moby/buildkit/session"
|
||||||
|
"github.com/moby/buildkit/session/secrets/secretsprovider"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateSecrets(secrets []*Secret) (session.Attachable, error) {
|
||||||
|
fs := make([]secretsprovider.Source, 0, len(secrets))
|
||||||
|
for _, secret := range secrets {
|
||||||
|
fs = append(fs, secretsprovider.Source{
|
||||||
|
ID: secret.ID,
|
||||||
|
FilePath: secret.FilePath,
|
||||||
|
Env: secret.Env,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
store, err := secretsprovider.NewStore(fs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return secretsprovider.NewSecretProvider(store), nil
|
||||||
|
}
|
||||||
18
controller/pb/ssh.go
Normal file
18
controller/pb/ssh.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package pb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/moby/buildkit/session"
|
||||||
|
"github.com/moby/buildkit/session/sshforward/sshprovider"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateSSH(ssh []*SSH) (session.Attachable, error) {
|
||||||
|
configs := make([]sshprovider.AgentConfig, 0, len(ssh))
|
||||||
|
for _, ssh := range ssh {
|
||||||
|
cfg := sshprovider.AgentConfig{
|
||||||
|
ID: ssh.ID,
|
||||||
|
Paths: append([]string{}, ssh.Paths...),
|
||||||
|
}
|
||||||
|
configs = append(configs, cfg)
|
||||||
|
}
|
||||||
|
return sshprovider.NewSSHAgentProvider(configs)
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user