mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-08-30 23:49:11 +08:00
Compare commits
1725 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89154c7d33 | ||
|
|
1a03187338 | ||
|
|
b3e7827871 | ||
|
|
d43cf8c2c6 | ||
|
|
c954a45352 | ||
|
|
ce521a3a85 | ||
|
|
29f879990e | ||
|
|
5e3bf8713a | ||
|
|
57f7f1becc | ||
|
|
091214d59d | ||
|
|
5f26f514a1 | ||
|
|
454306a8ef | ||
|
|
30feaa1a91 | ||
|
|
8fb1163577 | ||
|
|
b68ee824c6 | ||
|
|
2175f9ec7c | ||
|
|
ba1ee7af6e | ||
|
|
565b0b8991 | ||
|
|
a494e9ccc4 | ||
|
|
542e5d810e | ||
|
|
89fb005922 | ||
|
|
d353f6c426 | ||
|
|
2271096e46 | ||
|
|
95062ce8df | ||
|
|
255aff71fb | ||
|
|
d537b9e418 | ||
|
|
616fb3e55c | ||
|
|
80aa28f75c | ||
|
|
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 | ||
|
|
fb7b670b76 | ||
|
|
9ac5b075cf | ||
|
|
0124b6b9c9 | ||
|
|
691a1479fc | ||
|
|
c9d69b082b | ||
|
|
e24e04be57 | ||
|
|
38f9241316 | ||
|
|
3862ff269b | ||
|
|
9e5321eab8 | ||
|
|
f2cf7cf281 | ||
|
|
8961d3573e | ||
|
|
26570d05c1 | ||
|
|
8627f668f2 | ||
|
|
d8ca46066d | ||
|
|
c00c5a89e5 | ||
|
|
14b7936c3b | ||
|
|
40b41ac6e4 | ||
|
|
fd6de6b6ae | ||
|
|
f3111bcbef | ||
|
|
e6be472831 | ||
|
|
e5217f26e2 | ||
|
|
7f7acf7837 | ||
|
|
baae4b2e71 | ||
|
|
42448c5f37 | ||
|
|
fc7875675c | ||
|
|
355261e49e | ||
|
|
44c840b31d | ||
|
|
1bc068a583 | ||
|
|
340686a383 | ||
|
|
1ad87c6ba6 | ||
|
|
eadf5eddbc | ||
|
|
f4f58003fb | ||
|
|
bda4882a65 | ||
|
|
77ddee9314 | ||
|
|
c9676c79d1 | ||
|
|
18095ee87b | ||
|
|
c4d07f67e3 |
@@ -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.
|
||||||
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
302
.github/workflows/build.yml
vendored
Normal file
302
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
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"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare-test:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
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: Build
|
||||||
|
uses: docker/bake-action@v4
|
||||||
|
with:
|
||||||
|
targets: integration-test-base
|
||||||
|
set: |
|
||||||
|
*.cache-from=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
|
||||||
|
*.cache-to=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs:
|
||||||
|
- prepare-test
|
||||||
|
env:
|
||||||
|
TESTFLAGS: "-v --parallel=6 --timeout=30m"
|
||||||
|
TESTFLAGS_DOCKER: "-v --parallel=1 --timeout=30m"
|
||||||
|
GOTESTSUM_FORMAT: "standard-verbose"
|
||||||
|
TEST_IMAGE_BUILD: "0"
|
||||||
|
TEST_IMAGE_ID: "buildx-tests"
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
worker:
|
||||||
|
- docker
|
||||||
|
- docker\+containerd # same as docker, but with containerd snapshotter
|
||||||
|
- docker-container
|
||||||
|
- remote
|
||||||
|
pkg:
|
||||||
|
- ./tests
|
||||||
|
include:
|
||||||
|
- pkg: ./...
|
||||||
|
skip-integration-tests: 1
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
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: |
|
||||||
|
*.cache-from=type=gha,scope=${{ env.TEST_CACHE_SCOPE }}
|
||||||
|
*.output=type=docker,name=${{ env.TEST_IMAGE_ID }}
|
||||||
|
-
|
||||||
|
name: Test
|
||||||
|
run: |
|
||||||
|
export TEST_REPORT_SUFFIX=-${{ github.job }}-$(echo "${{ matrix.pkg }}-${{ matrix.skip-integration-tests }}-${{ matrix.worker }}" | tr -dc '[:alnum:]-\n\r' | tr '[:upper:]' '[:lower:]')
|
||||||
|
./hack/test
|
||||||
|
env:
|
||||||
|
TEST_DOCKERD: "${{ startsWith(matrix.worker, 'docker') && '1' || '0' }}"
|
||||||
|
TESTFLAGS: "${{ (matrix.worker == 'docker' || matrix.worker == 'docker\\+containerd') && env.TESTFLAGS_DOCKER || env.TESTFLAGS }} --run=//worker=${{ matrix.worker }}$"
|
||||||
|
TESTPKGS: "${{ matrix.pkg }}"
|
||||||
|
SKIP_INTEGRATION_TESTS: "${{ matrix.skip-integration-tests }}"
|
||||||
|
-
|
||||||
|
name: Send to Codecov
|
||||||
|
if: always()
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
with:
|
||||||
|
directory: ./bin/testreports
|
||||||
|
-
|
||||||
|
name: Generate annotations
|
||||||
|
if: always()
|
||||||
|
uses: crazy-max/.github/.github/actions/gotest-annotations@1a64ea6d01db9a48aa61954cb20e265782c167d9
|
||||||
|
with:
|
||||||
|
directory: ./bin/testreports
|
||||||
|
-
|
||||||
|
name: Upload test reports
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: test-reports
|
||||||
|
path: ./bin/testreports
|
||||||
|
|
||||||
|
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@v3
|
||||||
|
with:
|
||||||
|
name: buildx
|
||||||
|
path: ${{ env.DESTDIR }}/*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
bin-image:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
needs:
|
||||||
|
- test
|
||||||
|
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
|
||||||
|
- binaries
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Download binaries
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: buildx
|
||||||
|
path: ${{ env.DESTDIR }}
|
||||||
|
-
|
||||||
|
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@de2c0eb89ae2a093876385947365aca7b0e5f844 # v0.1.15
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
draft: true
|
||||||
|
files: ${{ env.DESTDIR }}/*
|
||||||
|
|
||||||
|
buildkit-edge:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
continue-on-error: true
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@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=moby/buildkit:master
|
||||||
|
buildkitd-flags: --debug
|
||||||
|
-
|
||||||
|
# Just run a bake target to check eveything runs fine
|
||||||
|
name: Build
|
||||||
|
uses: docker/bake-action@v4
|
||||||
|
with:
|
||||||
|
targets: binaries
|
||||||
42
.github/workflows/codeql.yml
vendored
Normal file
42
.github/workflows/codeql.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: codeql
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v[0-9]*'
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: 1.21.6
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
codeql:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Set up Go
|
||||||
|
uses: actions/setup-go@v4
|
||||||
|
with:
|
||||||
|
go-version: ${{ env.GO_VERSION }}
|
||||||
|
-
|
||||||
|
name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
with:
|
||||||
|
languages: go
|
||||||
|
-
|
||||||
|
name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
-
|
||||||
|
name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
|
with:
|
||||||
|
category: "/language:go"
|
||||||
58
.github/workflows/docs-release.yml
vendored
Normal file
58
.github/workflows/docs-release.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
name: docs-release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- released
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
open-pr:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
if: ${{ github.event.release.prerelease != true && 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/*
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
-
|
||||||
|
name: Build docs
|
||||||
|
uses: docker/bake-action@v4
|
||||||
|
with:
|
||||||
|
source: ${{ github.server_url }}/${{ github.repository }}.git#${{ github.event.release.name }}
|
||||||
|
targets: update-docs
|
||||||
|
set: |
|
||||||
|
*.output=/tmp/buildx-docs
|
||||||
|
env:
|
||||||
|
DOCS_FORMATS: yaml
|
||||||
|
-
|
||||||
|
name: Copy files
|
||||||
|
run: |
|
||||||
|
cp /tmp/buildx-docs/out/reference/*.yaml ./_data/buildx/
|
||||||
|
-
|
||||||
|
name: Commit changes
|
||||||
|
run: |
|
||||||
|
git add -A .
|
||||||
|
-
|
||||||
|
name: Create PR on docs repo
|
||||||
|
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||||
|
push-to-fork: docker-tools-robot/docker.github.io
|
||||||
|
commit-message: "build: update buildx reference to ${{ github.event.release.name }}"
|
||||||
|
signoff: true
|
||||||
|
branch: dispatch/buildx-ref-${{ github.event.release.name }}
|
||||||
|
delete-branch: true
|
||||||
|
title: Update buildx reference to ${{ github.event.release.name }}
|
||||||
|
body: |
|
||||||
|
Update the buildx reference documentation to keep in sync with the latest release `${{ github.event.release.name }}`
|
||||||
|
draft: false
|
||||||
62
.github/workflows/docs-upstream.yml
vendored
Normal file
62
.github/workflows/docs-upstream.yml
vendored
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# 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
|
||||||
|
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@v3
|
||||||
|
with:
|
||||||
|
name: docs-yaml
|
||||||
|
path: /tmp/buildx-docs/out/reference
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
validate:
|
||||||
|
uses: docker/docs/.github/workflows/validate-upstream.yml@e864c797e391fa35aecf1ddcecdbdeb683546424 # pin for artifact v3 support
|
||||||
|
needs:
|
||||||
|
- docs-yaml
|
||||||
|
with:
|
||||||
|
module-name: docker/buildx
|
||||||
|
data-files-id: docs-yaml
|
||||||
|
data-files-folder: buildx
|
||||||
|
data-files-placeholder-folder: engine/reference/commandline
|
||||||
216
.github/workflows/e2e.yml
vendored
Normal file
216
.github/workflows/e2e.yml
vendored
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
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@v3
|
||||||
|
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
|
||||||
|
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@v3
|
||||||
|
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
|
||||||
|
-
|
||||||
|
name: Install k3s
|
||||||
|
if: matrix.driver == 'kubernetes'
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
let wait = function(milliseconds) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (typeof(milliseconds) !== 'number') {
|
||||||
|
throw new Error('milleseconds not a number');
|
||||||
|
}
|
||||||
|
setTimeout(() => resolve("done!"), milliseconds)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const kubeconfig="/tmp/buildkit-k3s/kubeconfig.yaml";
|
||||||
|
core.info(`storing kubeconfig in ${kubeconfig}`);
|
||||||
|
|
||||||
|
await exec.exec('docker', ["run", "-d",
|
||||||
|
"--privileged",
|
||||||
|
"--name=buildkit-k3s",
|
||||||
|
"-e", "K3S_KUBECONFIG_OUTPUT="+kubeconfig,
|
||||||
|
"-e", "K3S_KUBECONFIG_MODE=666",
|
||||||
|
"-v", "/tmp/buildkit-k3s:/tmp/buildkit-k3s",
|
||||||
|
"-p", "6443:6443",
|
||||||
|
"-p", "80:80",
|
||||||
|
"-p", "443:443",
|
||||||
|
"-p", "8080:8080",
|
||||||
|
"rancher/k3s:${{ env.K3S_VERSION }}", "server"
|
||||||
|
]);
|
||||||
|
await wait(10000);
|
||||||
|
|
||||||
|
core.exportVariable('KUBECONFIG', kubeconfig);
|
||||||
|
|
||||||
|
let nodeName;
|
||||||
|
for (let count = 1; count <= 5; count++) {
|
||||||
|
try {
|
||||||
|
const nodeNameOutput = await exec.getExecOutput("kubectl get nodes --no-headers -oname");
|
||||||
|
nodeName = nodeNameOutput.stdout
|
||||||
|
} catch (error) {
|
||||||
|
core.info(`Unable to resolve node name (${error.message}). Attempt ${count} of 5.`)
|
||||||
|
} finally {
|
||||||
|
if (nodeName) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
await wait(5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!nodeName) {
|
||||||
|
throw new Error(`Unable to resolve node name after 5 attempts.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await exec.exec(`kubectl wait --for=condition=Ready ${nodeName}`);
|
||||||
|
} catch (error) {
|
||||||
|
core.setFailed(error.message);
|
||||||
|
}
|
||||||
|
-
|
||||||
|
name: Print KUBECONFIG
|
||||||
|
if: matrix.driver == 'kubernetes'
|
||||||
|
run: |
|
||||||
|
yq ${{ env.KUBECONFIG }}
|
||||||
|
-
|
||||||
|
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 }}
|
||||||
42
.github/workflows/validate.yml
vendored
Normal file
42
.github/workflows/validate.yml
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
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: 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: 10m
|
||||||
|
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>
|
||||||
|
|||||||
138
Dockerfile
138
Dockerfile
@@ -1,80 +1,144 @@
|
|||||||
# syntax=docker/dockerfile:1.1-experimental
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
ARG DOCKERD_VERSION=19.03-rc
|
ARG GO_VERSION=1.21.6
|
||||||
ARG CLI_VERSION=19.03
|
ARG XX_VERSION=1.2.1
|
||||||
|
|
||||||
FROM docker:$DOCKERD_VERSION AS dockerd-release
|
ARG DOCKER_VERSION=24.0.6
|
||||||
|
ARG GOTESTSUM_VERSION=v1.9.0
|
||||||
|
ARG REGISTRY_VERSION=2.8.0
|
||||||
|
ARG BUILDKIT_VERSION=v0.11.6
|
||||||
|
|
||||||
# xgo is a helper for golang cross-compilation
|
# xx is a helper for cross-compilation
|
||||||
FROM --platform=$BUILDPLATFORM tonistiigi/xx:golang@sha256:6f7d999551dd471b58f70716754290495690efa8421e0a1fcf18eb11d0c0a537 AS xgo
|
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
|
||||||
|
|
||||||
FROM --platform=$BUILDPLATFORM golang:1.13-alpine AS gobase
|
FROM --platform=$BUILDPLATFORM golang:${GO_VERSION}-alpine AS golatest
|
||||||
COPY --from=xgo / /
|
|
||||||
|
FROM golatest AS gobase
|
||||||
|
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 registry:$REGISTRY_VERSION AS registry
|
||||||
|
|
||||||
|
FROM moby/buildkit:$BUILDKIT_VERSION AS buildkit
|
||||||
|
|
||||||
|
FROM gobase AS docker
|
||||||
|
ARG TARGETPLATFORM
|
||||||
|
ARG DOCKER_VERSION
|
||||||
|
WORKDIR /opt/docker
|
||||||
|
RUN DOCKER_ARCH=$(case ${TARGETPLATFORM:-linux/amd64} in \
|
||||||
|
"linux/amd64") echo "x86_64" ;; \
|
||||||
|
"linux/arm/v6") echo "armel" ;; \
|
||||||
|
"linux/arm/v7") echo "armhf" ;; \
|
||||||
|
"linux/arm64") echo "aarch64" ;; \
|
||||||
|
"linux/ppc64le") echo "ppc64le" ;; \
|
||||||
|
"linux/s390x") echo "s390x" ;; \
|
||||||
|
*) echo "" ;; esac) \
|
||||||
|
&& echo "DOCKER_ARCH=$DOCKER_ARCH" \
|
||||||
|
&& wget -qO- "https://download.docker.com/linux/static/stable/${DOCKER_ARCH}/docker-${DOCKER_VERSION}.tgz" | tar xvz --strip 1
|
||||||
|
RUN ./dockerd --version && ./containerd --version && ./ctr --version && ./runc --version
|
||||||
|
|
||||||
|
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 /opt/docker/* /usr/bin/
|
||||||
|
COPY --link --from=buildkit /usr/bin/buildkitd /usr/bin/
|
||||||
|
COPY --link --from=buildkit /usr/bin/buildctl /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/
|
||||||
VOLUME /var/lib/docker
|
VOLUME /var/lib/docker
|
||||||
ENTRYPOINT ["entrypoint.sh"]
|
ENTRYPOINT ["entrypoint.sh"]
|
||||||
|
|
||||||
FROM binaries
|
FROM binaries
|
||||||
|
|||||||
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"
|
||||||
|
|||||||
87
Makefile
87
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
|
||||||
|
|
||||||
validate-vendor:
|
.PHONY: test-unit
|
||||||
./hack/validate-vendor
|
test-unit:
|
||||||
|
TESTPKGS=./... SKIP_INTEGRATION_TESTS=1 ./hack/test
|
||||||
validate-all: lint test validate-vendor
|
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test-integration:
|
||||||
|
TESTPKGS=./tests ./hack/test
|
||||||
|
|
||||||
|
.PHONY: validate-vendor
|
||||||
|
validate-vendor:
|
||||||
|
$(BUILDX_CMD) bake 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
|
||||||
|
|||||||
1322
bake/bake.go
1322
bake/bake.go
File diff suppressed because it is too large
Load Diff
1414
bake/bake_test.go
1414
bake/bake_test.go
File diff suppressed because it is too large
Load Diff
339
bake/compose.go
339
bake/compose.go
@@ -1,50 +1,50 @@
|
|||||||
package bake
|
package bake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/cli/cli/compose/loader"
|
"github.com/compose-spec/compose-go/dotenv"
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
"github.com/compose-spec/compose-go/loader"
|
||||||
|
compose "github.com/compose-spec/compose-go/types"
|
||||||
|
"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 []compose.ConfigFile
|
||||||
ConfigFiles: []composetypes.ConfigFile{
|
for _, f := range fs {
|
||||||
{
|
cfgs = append(cfgs, compose.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 []compose.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(), compose.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 +52,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 +72,65 @@ 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 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,
|
||||||
|
}
|
||||||
|
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 +142,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(compose.ConfigDetails{
|
||||||
|
ConfigFiles: []compose.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 compose.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 compose.ServiceSecretConfig, psecret compose.SecretConfig) (string, error) {
|
||||||
|
if psecret.External.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"
|
||||||
|
|
||||||
|
compose "github.com/compose-spec/compose-go/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,88 @@ 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)
|
cwd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
c, err := ParseCompose([]compose.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, filepath.Join(cwd, "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, filepath.Join(cwd, "dir"), *c.Targets[1].Context)
|
||||||
|
require.Equal(t, map[string]string{"foo": filepath.Join(cwd, "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, filepath.Join(cwd, "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([]compose.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 +114,7 @@ services:
|
|||||||
target: webapp
|
target: webapp
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]compose.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 +129,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 +139,619 @@ services:
|
|||||||
target: webapp
|
target: webapp
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseCompose(dt)
|
c, err := ParseCompose([]compose.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([]compose.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([]compose.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([]compose.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([]compose.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([]compose.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
|
||||||
|
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([]compose.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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([]compose.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([]compose.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([]compose.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([]compose.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([]compose.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([]compose.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([]compose.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: "compose.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([]compose.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)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
181
bake/hcl.go
181
bake/hcl.go
@@ -1,133 +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/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,
|
return f, true, err
|
||||||
"bytesslice": stdlib.BytesSliceFunc,
|
|
||||||
"csvdecode": stdlib.CSVDecodeFunc,
|
|
||||||
"coalesce": stdlib.CoalesceFunc,
|
|
||||||
"concat": stdlib.ConcatFunc,
|
|
||||||
"divide": stdlib.DivideFunc,
|
|
||||||
"equal": stdlib.EqualFunc,
|
|
||||||
"formatdate": stdlib.FormatDateFunc,
|
|
||||||
"format": stdlib.FormatFunc,
|
|
||||||
"formatlist": stdlib.FormatListFunc,
|
|
||||||
"greaterthan": stdlib.GreaterThanFunc,
|
|
||||||
"greaterthanorequalto": stdlib.GreaterThanOrEqualToFunc,
|
|
||||||
"hasindex": stdlib.HasIndexFunc,
|
|
||||||
"index": stdlib.IndexFunc,
|
|
||||||
"int": stdlib.IntFunc,
|
|
||||||
"jsondecode": stdlib.JSONDecodeFunc,
|
|
||||||
"jsonencode": stdlib.JSONEncodeFunc,
|
|
||||||
"length": stdlib.LengthFunc,
|
|
||||||
"lessthan": stdlib.LessThanFunc,
|
|
||||||
"lessthanorequalto": stdlib.LessThanOrEqualToFunc,
|
|
||||||
"lower": stdlib.LowerFunc,
|
|
||||||
"max": stdlib.MaxFunc,
|
|
||||||
"min": stdlib.MinFunc,
|
|
||||||
"modulo": stdlib.ModuloFunc,
|
|
||||||
"multiply": stdlib.MultiplyFunc,
|
|
||||||
"negate": stdlib.NegateFunc,
|
|
||||||
"notequal": stdlib.NotEqualFunc,
|
|
||||||
"not": stdlib.NotFunc,
|
|
||||||
"or": stdlib.OrFunc,
|
|
||||||
"range": stdlib.RangeFunc,
|
|
||||||
"regexall": stdlib.RegexAllFunc,
|
|
||||||
"regex": stdlib.RegexFunc,
|
|
||||||
"reverse": stdlib.ReverseFunc,
|
|
||||||
"sethaselement": stdlib.SetHasElementFunc,
|
|
||||||
"setintersection": stdlib.SetIntersectionFunc,
|
|
||||||
"setsubtract": stdlib.SetSubtractFunc,
|
|
||||||
"setsymmetricdifference": stdlib.SetSymmetricDifferenceFunc,
|
|
||||||
"setunion": stdlib.SetUnionFunc,
|
|
||||||
"strlen": stdlib.StrlenFunc,
|
|
||||||
"substr": stdlib.SubstrFunc,
|
|
||||||
"subtract": stdlib.SubtractFunc,
|
|
||||||
"upper": stdlib.UpperFunc,
|
|
||||||
}
|
}
|
||||||
)
|
if strings.HasSuffix(fn, ".hcl") {
|
||||||
|
f, diags := hclparse.NewParser().ParseHCL(dt, fn)
|
||||||
// Used in the first pass of decoding instead of the Config struct to disallow
|
if diags.HasErrors() {
|
||||||
// interpolation while parsing variable blocks.
|
err = diags
|
||||||
type staticConfig struct {
|
}
|
||||||
Variables []*Variable `hcl:"variable,block"`
|
return f, true, err
|
||||||
Remain hcl.Body `hcl:",remain"`
|
}
|
||||||
|
f, diags := hclparse.NewParser().ParseHCL(dt, fn+".hcl")
|
||||||
|
if diags.HasErrors() {
|
||||||
|
f, diags2 := hclparse.NewParser().ParseJSON(dt, fn+".json")
|
||||||
|
if !diags2.HasErrors() {
|
||||||
|
return f, true, nil
|
||||||
|
}
|
||||||
|
return nil, false, diags
|
||||||
|
}
|
||||||
|
return f, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseHCL(dt []byte, fn string) (*Config, error) {
|
func formatHCLError(err error, files []File) error {
|
||||||
// Decode user defined functions.
|
if err == nil {
|
||||||
file, diags := hclsyntax.ParseConfig(dt, fn, hcl.Pos{Line: 1, Column: 1})
|
return nil
|
||||||
if diags.HasErrors() {
|
|
||||||
return nil, diags
|
|
||||||
}
|
}
|
||||||
|
diags, ok := err.(hcl.Diagnostics)
|
||||||
userFunctions, _, diags := userfunc.DecodeUserFunctions(file.Body, "function", func() *hcl.EvalContext {
|
if !ok {
|
||||||
return &hcl.EvalContext{
|
return err
|
||||||
Functions: stdlibFunctions,
|
}
|
||||||
|
for _, d := range diags {
|
||||||
|
if d.Severity != hcl.DiagError {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
})
|
if d.Subject != nil {
|
||||||
if diags.HasErrors() {
|
var dt []byte
|
||||||
return nil, diags
|
for _, f := range files {
|
||||||
}
|
if d.Subject.Filename == f.Name {
|
||||||
|
dt = f.Data
|
||||||
var sc staticConfig
|
break
|
||||||
|
}
|
||||||
// Decode only variable blocks without interpolation.
|
}
|
||||||
if err := hclsimple.Decode(fn, dt, nil, &sc); err != nil {
|
src := errdefs.Source{
|
||||||
return nil, err
|
Info: &pb.SourceInfo{
|
||||||
}
|
Filename: d.Subject.Filename,
|
||||||
|
Data: dt,
|
||||||
// Set all variables to their default value if defined.
|
},
|
||||||
variables := make(map[string]cty.Value)
|
Ranges: []*pb.Range{toErrRange(d.Subject)},
|
||||||
for _, variable := range sc.Variables {
|
}
|
||||||
variables[variable.Name] = cty.StringVal(variable.Default)
|
err = errdefs.WithSource(err, src)
|
||||||
}
|
break
|
||||||
|
|
||||||
// Override default with values from environment.
|
|
||||||
for _, env := range os.Environ() {
|
|
||||||
parts := strings.SplitN(env, "=", 2)
|
|
||||||
name, value := parts[0], parts[1]
|
|
||||||
if _, ok := variables[name]; ok {
|
|
||||||
variables[name] = cty.StringVal(value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
1424
bake/hcl_test.go
1424
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
|
||||||
|
}
|
||||||
135
bake/hclparser/stdlib.go
Normal file
135
bake/hclparser/stdlib.go
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
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/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,
|
||||||
|
"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,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
200
bake/remote.go
Normal file
200
bake/remote.go
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
package bake
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"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 session []session.Attachable
|
||||||
|
var filename string
|
||||||
|
st, ok := dockerui.DetectGitContext(url, false)
|
||||||
|
if ok {
|
||||||
|
ssh, err := controllerapi.CreateSSH([]*controllerapi.SSH{{ID: "default"}})
|
||||||
|
if err == nil {
|
||||||
|
session = append(session, ssh)
|
||||||
|
}
|
||||||
|
} 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: session, 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
|
||||||
|
}
|
||||||
1427
build/build.go
1427
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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
115
build/git.go
Normal file
115
build/git.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/gitutil"
|
||||||
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const DockerfileLabel = "com.docker.image.source.entrypoint"
|
||||||
|
|
||||||
|
func getGitAttributes(ctx context.Context, contextPath string, dockerfilePath string) (res map[string]string, _ error) {
|
||||||
|
res = make(map[string]string)
|
||||||
|
if contextPath == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// figure out in which directory the git command needs to run in
|
||||||
|
var wd string
|
||||||
|
if filepath.IsAbs(contextPath) {
|
||||||
|
wd = contextPath
|
||||||
|
} else {
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
wd, _ = filepath.Abs(filepath.Join(cwd, contextPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
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, errors.Wrap(err, "git was not found in the system")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !gitc.IsInsideWorkTree() {
|
||||||
|
if st, err := os.Stat(path.Join(wd, ".git")); err == nil && st.IsDir() {
|
||||||
|
return res, errors.New("failed to read current commit information with git rev-parse --is-inside-work-tree")
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if sha, err := gitc.FullCommit(); err != nil && !gitutil.IsUnknownRevision(err) {
|
||||||
|
return res, 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 {
|
||||||
|
if root, err := gitc.RootDir(); err != nil {
|
||||||
|
return res, errors.Wrap(err, "failed to get git root dir")
|
||||||
|
} else if root != "" {
|
||||||
|
if dockerfilePath == "" {
|
||||||
|
dockerfilePath = filepath.Join(wd, "Dockerfile")
|
||||||
|
}
|
||||||
|
if !filepath.IsAbs(dockerfilePath) {
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
dockerfilePath = filepath.Join(cwd, dockerfilePath)
|
||||||
|
}
|
||||||
|
dockerfilePath, _ = filepath.Rel(root, dockerfilePath)
|
||||||
|
if !strings.HasPrefix(dockerfilePath, "..") {
|
||||||
|
res["label:"+DockerfileLabel] = dockerfilePath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
156
build/git_test.go
Normal file
156
build/git_test.go
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/gitutil"
|
||||||
|
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"))
|
||||||
|
}
|
||||||
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
|
||||||
|
}
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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,15 +3,36 @@ package build
|
|||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 IsRemoteURL(c string) bool {
|
||||||
|
if urlutil.IsURL(c) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if _, err := gitutil.ParseGitRef(c); err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func isLocalDir(c string) bool {
|
func isLocalDir(c string) bool {
|
||||||
st, err := os.Stat(c)
|
st, err := os.Stat(c)
|
||||||
@@ -38,18 +59,40 @@ 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 || host == "" || ip == "" {
|
||||||
if len(parts) != 2 || parts[0] == "" || net.ParseIP(parts[1]) == nil {
|
|
||||||
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 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
|
||||||
|
}
|
||||||
|
|||||||
299
builder/builder.go
Normal file
299
builder/builder.go
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/driver"
|
||||||
|
"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/progress"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
243
builder/node.go
Normal file
243
builder/node.go
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
package builder
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"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, "buildx_buildkit_"+n.Name, factory, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, 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) 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
|
||||||
|
}
|
||||||
@@ -5,50 +5,91 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
"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"
|
||||||
|
|
||||||
|
//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() {
|
||||||
|
//nolint:staticcheck
|
||||||
|
seed.WithTimeAndRand()
|
||||||
|
|
||||||
|
stack.SetVersionInfo(version.Version, version.Revision)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runStandalone(cmd *command.DockerCli) error {
|
||||||
|
if err := cmd.Initialize(cliflags.NewClientOptions()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rootCmd := commands.NewRootCmd(os.Args[0], false, cmd)
|
||||||
|
return rootCmd.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
307
commands/bake.go
307
commands/bake.go
@@ -1,36 +1,72 @@
|
|||||||
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/identity"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
|
"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(dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
if len(in.files) == 0 {
|
ctx, end, err := tracing.TraceCurrentCommand(ctx, "bake")
|
||||||
files, err := defaultFiles()
|
if err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
|
defer func() {
|
||||||
|
end(err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var url string
|
||||||
|
cmdContext := "cwd://"
|
||||||
|
|
||||||
|
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 {
|
||||||
@@ -42,24 +78,128 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) error {
|
|||||||
if in.exportLoad {
|
if in.exportLoad {
|
||||||
return errors.Errorf("push and load may not be set together at the moment")
|
return errors.Errorf("push and load may not be set together at the moment")
|
||||||
}
|
}
|
||||||
overrides = append(overrides, "*.output=type=registry")
|
overrides = append(overrides, "*.push=true")
|
||||||
} else if in.exportLoad {
|
} else if in.exportLoad {
|
||||||
overrides = append(overrides, "*.output=type=docker")
|
overrides = append(overrides, "*.output=type=docker")
|
||||||
}
|
}
|
||||||
if in.noCache != nil {
|
if cFlags.noCache != nil {
|
||||||
overrides = append(overrides, fmt.Sprintf("*.no-cache=%t", *in.noCache))
|
overrides = append(overrides, fmt.Sprintf("*.no-cache=%t", *cFlags.noCache))
|
||||||
}
|
}
|
||||||
if in.pull != nil {
|
if cFlags.pull != nil {
|
||||||
overrides = append(overrides, fmt.Sprintf("*.pull=%t", *in.pull))
|
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
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
progressConsoleDesc = fmt.Sprintf("%s:%s", b.Driver, b.Name)
|
||||||
|
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 {
|
||||||
|
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,59 +207,128 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
bo, err := bake.TargetsToBuildOpt(m)
|
// local state group
|
||||||
|
groupRef := identity.NewID()
|
||||||
|
var refs []string
|
||||||
|
for k, b := range bo {
|
||||||
|
b.Ref = identity.NewID()
|
||||||
|
b.GroupRef = groupRef
|
||||||
|
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...]",
|
||||||
Aliases: []string{"f"},
|
Aliases: []string{"f"},
|
||||||
Short: "Build from a file",
|
Short: "Build from a file",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runBake(dockerCli, args, options)
|
// reset to nil to avoid override is unset
|
||||||
|
if !cmd.Flags().Lookup("no-cache").Changed {
|
||||||
|
cFlags.noCache = nil
|
||||||
|
}
|
||||||
|
if !cmd.Flags().Lookup("pull").Changed {
|
||||||
|
cFlags.pull = nil
|
||||||
|
}
|
||||||
|
options.builder = rootOpts.builder
|
||||||
|
options.metadataFile = cFlags.metadataFile
|
||||||
|
// Other common flags (noCache, pull and progress) are processed in runBake function.
|
||||||
|
return runBake(dockerCli, args, options, cFlags)
|
||||||
},
|
},
|
||||||
|
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
|
||||||
|
var rnames []string
|
||||||
|
for _, v := range names {
|
||||||
|
if strings.HasPrefix(v, "cwd://") {
|
||||||
|
lnames = append(lnames, strings.TrimPrefix(v, "cwd://"))
|
||||||
|
} else {
|
||||||
|
rnames = append(rnames, 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(append(lnames, rnames...), stdin, sub)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
files = append(files, lfiles...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
1053
commands/build.go
1053
commands/build.go
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,29 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/csv"
|
"encoding/csv"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/builder"
|
||||||
"github.com/docker/buildx/driver"
|
"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"
|
||||||
|
"github.com/docker/buildx/store/storeutil"
|
||||||
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/util/dockerutil"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
|
dopts "github.com/docker/cli/opts"
|
||||||
"github.com/google/shlex"
|
"github.com/google/shlex"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -28,6 +42,7 @@ type createOptions struct {
|
|||||||
flags string
|
flags string
|
||||||
configFile string
|
configFile string
|
||||||
driverOpts []string
|
driverOpts []string
|
||||||
|
bootstrap bool
|
||||||
// upgrade bool // perform upgrade of the driver
|
// upgrade bool // perform upgrade of the driver
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,26 +68,11 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
driverName := in.driver
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
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
|
name := in.name
|
||||||
@@ -83,6 +83,19 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !in.actionLeave && !in.actionAppend {
|
||||||
|
contexts, err := dockerCli.ContextStore().List()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, c := range contexts {
|
||||||
|
if c.Name == name {
|
||||||
|
logrus.Warnf("instance name %q already exists as context builder", name)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ng, err := txn.NodeGroupByName(name)
|
ng, err := txn.NodeGroupByName(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(errors.Cause(err)) {
|
if os.IsNotExist(errors.Cause(err)) {
|
||||||
@@ -90,29 +103,62 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
logrus.Warnf("failed to find %q for append, creating a new instance instead", in.name)
|
logrus.Warnf("failed to find %q for append, creating a new instance instead", in.name)
|
||||||
}
|
}
|
||||||
if in.actionLeave {
|
if in.actionLeave {
|
||||||
return errors.Errorf("failed to find instance %q for leave", name)
|
return errors.Errorf("failed to find instance %q for leave", in.name)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildkitHost := os.Getenv("BUILDKIT_HOST")
|
||||||
|
|
||||||
|
driverName := in.driver
|
||||||
|
if driverName == "" {
|
||||||
|
if ng != nil {
|
||||||
|
driverName = ng.Driver
|
||||||
|
} else if len(args) == 0 && buildkitHost != "" {
|
||||||
|
driverName = "remote"
|
||||||
|
} else {
|
||||||
|
var arg string
|
||||||
|
if len(args) > 0 {
|
||||||
|
arg = args[0]
|
||||||
|
}
|
||||||
|
f, err := driver.GetDefaultFactory(ctx, arg, dockerCli.Client(), true, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if f == nil {
|
||||||
|
return errors.Errorf("no valid drivers found")
|
||||||
|
}
|
||||||
|
driverName = f.Name()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ng != nil {
|
if ng != nil {
|
||||||
if in.nodeName == "" && !in.actionAppend {
|
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)
|
return errors.Errorf("existing instance for %q but no append mode, specify --node to make changes for existing instances", name)
|
||||||
}
|
}
|
||||||
|
if driverName != ng.Driver {
|
||||||
|
return errors.Errorf("existing instance for %q but has mismatched driver %q", name, ng.Driver)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := driver.GetFactory(driverName, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOriginal := ng
|
||||||
|
if ngOriginal != nil {
|
||||||
|
ngOriginal = ngOriginal.Copy()
|
||||||
}
|
}
|
||||||
|
|
||||||
if ng == nil {
|
if ng == nil {
|
||||||
ng = &store.NodeGroup{
|
ng = &store.NodeGroup{
|
||||||
Name: name,
|
Name: name,
|
||||||
|
Driver: driverName,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ng.Driver == "" || in.driver != "" {
|
|
||||||
ng.Driver = driverName
|
|
||||||
}
|
|
||||||
|
|
||||||
var flags []string
|
var flags []string
|
||||||
if in.flags != "" {
|
if in.flags != "" {
|
||||||
flags, err = shlex.Split(in.flags)
|
flags, err = shlex.Split(in.flags)
|
||||||
@@ -122,31 +168,88 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ep string
|
var ep string
|
||||||
|
var setEp bool
|
||||||
if in.actionLeave {
|
if in.actionLeave {
|
||||||
if err := ng.Leave(in.nodeName); err != nil {
|
if err := ng.Leave(in.nodeName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ls, err := localstate.New(confutil.ConfigDir(dockerCli))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ls.RemoveBuilderNode(ng.Name, in.nodeName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if len(args) > 0 {
|
switch {
|
||||||
|
case driverName == "kubernetes":
|
||||||
|
if len(args) > 0 {
|
||||||
|
logrus.Warnf("kubernetes driver does not support endpoint args %q", args[0])
|
||||||
|
}
|
||||||
|
// generate node name if not provided to avoid duplicated endpoint
|
||||||
|
// error: https://github.com/docker/setup-buildx-action/issues/215
|
||||||
|
nodeName := in.nodeName
|
||||||
|
if nodeName == "" {
|
||||||
|
nodeName, err = k8sutil.GenerateNodeName(name, txn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// naming endpoint to make --append works
|
||||||
|
ep = (&url.URL{
|
||||||
|
Scheme: driverName,
|
||||||
|
Path: "/" + name,
|
||||||
|
RawQuery: (&url.Values{
|
||||||
|
"deployment": {nodeName},
|
||||||
|
"kubeconfig": {os.Getenv("KUBECONFIG")},
|
||||||
|
}).Encode(),
|
||||||
|
}).String()
|
||||||
|
setEp = false
|
||||||
|
case driverName == "remote":
|
||||||
|
if len(args) > 0 {
|
||||||
|
ep = args[0]
|
||||||
|
} else if buildkitHost != "" {
|
||||||
|
ep = buildkitHost
|
||||||
|
} else {
|
||||||
|
return errors.Errorf("no remote endpoint provided")
|
||||||
|
}
|
||||||
|
ep, err = validateBuildkitEndpoint(ep)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
setEp = true
|
||||||
|
case len(args) > 0:
|
||||||
ep, err = validateEndpoint(dockerCli, args[0])
|
ep, err = validateEndpoint(dockerCli, args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
setEp = true
|
||||||
|
default:
|
||||||
if dockerCli.CurrentContext() == "default" && dockerCli.DockerEndpoint().TLSData != nil {
|
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>`")
|
return errors.Errorf("could not create a builder instance with TLS data loaded from environment. Please use `docker context create <context-name>` to create a context for current environment and then create a builder instance with `docker buildx create <context-name>`")
|
||||||
}
|
}
|
||||||
|
ep, err = dockerutil.GetCurrentEndpoint(dockerCli)
|
||||||
ep, err = getCurrentEndpoint(dockerCli)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
setEp = false
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := csvToMap(in.driverOpts)
|
m, err := csvToMap(in.driverOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := ng.Update(in.nodeName, ep, in.platform, len(args) > 0, in.actionAppend, flags, in.configFile, m); err != nil {
|
|
||||||
|
if in.configFile == "" {
|
||||||
|
// if buildkit config is not provided, check if the default one is
|
||||||
|
// available and use it
|
||||||
|
if f, ok := confutil.DefaultConfigFile(dockerCli); ok {
|
||||||
|
logrus.Warnf("Using default BuildKit config in %s", f)
|
||||||
|
in.configFile = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ng.Update(in.nodeName, ep, in.platform, setEp, in.actionAppend, flags, in.configFile, m); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -155,8 +258,41 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b, err := builder.New(dockerCli,
|
||||||
|
builder.WithName(ng.Name),
|
||||||
|
builder.WithStore(txn),
|
||||||
|
builder.WithSkippedValidation(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
nodes, err := b.LoadNodes(timeoutCtx, builder.WithData())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, node := range nodes {
|
||||||
|
if err := node.Err; err != nil {
|
||||||
|
err := errors.Errorf("failed to initialize builder %s (%s): %s", ng.Name, node.Name, err)
|
||||||
|
var err2 error
|
||||||
|
if ngOriginal == nil {
|
||||||
|
err2 = txn.Remove(ng.Name)
|
||||||
|
} else {
|
||||||
|
err2 = txn.Save(ngOriginal)
|
||||||
|
}
|
||||||
|
if err2 != nil {
|
||||||
|
logrus.Warnf("Could not rollback to previous state: %s", err2)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if in.use && ep != "" {
|
if in.use && ep != "" {
|
||||||
current, err := getCurrentEndpoint(dockerCli)
|
current, err := dockerutil.GetCurrentEndpoint(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -165,6 +301,16 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The store is no longer used from this point.
|
||||||
|
// Release it so we aren't holding the file lock during the boot.
|
||||||
|
release()
|
||||||
|
|
||||||
|
if in.bootstrap {
|
||||||
|
if _, err = b.Boot(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("%s\n", ng.Name)
|
fmt.Printf("%s\n", ng.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -172,9 +318,12 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
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{
|
||||||
@@ -184,28 +333,34 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runCreate(dockerCli, options, args)
|
return runCreate(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.flags, "buildkitd-flags", "", "Flags for buildkitd daemon")
|
||||||
flags.StringVar(&options.configFile, "config", "", "BuildKit config file")
|
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.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) {
|
func csvToMap(in []string) (map[string]string, error) {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
m := make(map[string]string, len(in))
|
m := make(map[string]string, len(in))
|
||||||
for _, s := range in {
|
for _, s := range in {
|
||||||
csvReader := csv.NewReader(strings.NewReader(s))
|
csvReader := csv.NewReader(strings.NewReader(s))
|
||||||
@@ -223,3 +378,27 @@ func csvToMap(in []string) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
return m, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
26
commands/create_test.go
Normal file
26
commands/create_test.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCsvToMap(t *testing.T) {
|
||||||
|
d := []string{
|
||||||
|
"\"tolerations=key=foo,value=bar;key=foo2,value=bar2\",replicas=1",
|
||||||
|
"namespace=default",
|
||||||
|
}
|
||||||
|
r, err := csvToMap(d)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Contains(t, r, "tolerations")
|
||||||
|
require.Equal(t, r["tolerations"], "key=foo,value=bar;key=foo2,value=bar2")
|
||||||
|
|
||||||
|
require.Contains(t, r, "replicas")
|
||||||
|
require.Equal(t, r["replicas"], "1")
|
||||||
|
|
||||||
|
require.Contains(t, r, "namespace")
|
||||||
|
require.Equal(t, r["namespace"], "default")
|
||||||
|
}
|
||||||
96
commands/debug/root.go
Normal file
96
commands/debug/root.go
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
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/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,
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"experimentalCLI": "",
|
||||||
|
},
|
||||||
|
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
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&options.InvokeFlag, "invoke", "", "Launch a monitor with executing specified command")
|
||||||
|
flags.SetAnnotation("invoke", "experimentalCLI", nil)
|
||||||
|
flags.StringVar(&options.OnFlag, "on", "error", "When to launch the monitor ([always, error])")
|
||||||
|
flags.SetAnnotation("on", "experimentalCLI", nil)
|
||||||
|
|
||||||
|
flags.StringVar(&controlOptions.Root, "root", "", "Specify root directory of server to connect for the monitor")
|
||||||
|
flags.SetAnnotation("root", "experimentalCLI", nil)
|
||||||
|
flags.BoolVar(&controlOptions.Detach, "detach", runtime.GOOS == "linux", "Detach buildx server for the monitor (supported only on linux)")
|
||||||
|
flags.SetAnnotation("detach", "experimentalCLI", nil)
|
||||||
|
flags.StringVar(&controlOptions.ServerConfig, "server-config", "", "Specify buildx server config file for the monitor (used only when launching new server)")
|
||||||
|
flags.SetAnnotation("server-config", "experimentalCLI", nil)
|
||||||
|
flags.StringVar(&progressMode, "progress", "auto", `Set type of progress output ("auto", "plain", "tty") for the monitor. Use plain to show container output`)
|
||||||
|
|
||||||
|
for _, c := range children {
|
||||||
|
cmd.AddCommand(c.NewDebugger(&options))
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
@@ -4,16 +4,19 @@ import (
|
|||||||
"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/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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,25 +34,29 @@ func runDiskUsage(dockerCli command.Cli, opts duOptions) error {
|
|||||||
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 +69,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 {
|
||||||
@@ -109,7 +116,7 @@ func duCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
options.builder = rootOpts.builder
|
options.builder = rootOpts.builder
|
||||||
return runDiskUsage(dockerCli, options)
|
return runDiskUsage(dockerCli, options)
|
||||||
},
|
},
|
||||||
Annotations: map[string]string{"version": "1.00"},
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
@@ -126,20 +133,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 +167,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 +197,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,20 @@
|
|||||||
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/appcontext"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
|
"github.com/moby/buildkit/util/progress/progressui"
|
||||||
"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,10 +23,13 @@ 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(dockerCli command.Cli, in createOptions, args []string) error {
|
||||||
@@ -35,7 +43,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 +83,48 @@ 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 {
|
for i, s := range srcs {
|
||||||
if s.Ref == nil && s.Desc.MediaType == "" && s.Desc.Digest != "" {
|
if s.Ref == nil {
|
||||||
n, err := reference.ParseNormalizedNamed(repo)
|
if defaultRepo == nil {
|
||||||
|
return errors.Errorf("multiple repositories specified, cannot infer repository for %q", args[i])
|
||||||
|
}
|
||||||
|
n, err := reference.ParseNormalizedNamed(*defaultRepo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r, err := reference.WithDigest(n, s.Desc.Digest)
|
if s.Desc.MediaType == "" && s.Desc.Digest != "" {
|
||||||
if err != nil {
|
r, err := reference.WithDigest(n, s.Desc.Digest)
|
||||||
return err
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
srcs[i].Ref = r
|
||||||
|
sourceRefs = true
|
||||||
|
} else {
|
||||||
|
srcs[i].Ref = reference.TagNameOnly(n)
|
||||||
}
|
}
|
||||||
srcs[i].Ref = r
|
|
||||||
sourceRefs = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
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 +138,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 +156,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 +167,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 +237,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 +252,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 {
|
||||||
|
options.builder = *opts.Builder
|
||||||
return runCreate(dockerCli, options, args)
|
return runCreate(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,67 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/docker/buildx/builder"
|
||||||
"os"
|
"github.com/docker/buildx/util/cobrautil/completion"
|
||||||
|
|
||||||
"github.com/containerd/containerd/images"
|
|
||||||
"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/moby/buildkit/util/appcontext"
|
||||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
"github.com/pkg/errors"
|
||||||
"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(dockerCli command.Cli, in inspectOptions, name string) error {
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
r := imagetools.New(imagetools.Opt{
|
if in.format != "" && in.raw {
|
||||||
Auth: dockerCli.ConfigFile(),
|
return errors.Errorf("format and raw cannot be used together")
|
||||||
})
|
}
|
||||||
|
|
||||||
dt, desc, err := r.Get(ctx, name)
|
b, err := builder.New(dockerCli, builder.WithName(in.builder))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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 {
|
||||||
|
options.builder = *rootOpts.Builder
|
||||||
return runInspect(dockerCli, options, args[0])
|
return runInspect(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,21 @@ 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/docker/cli/cli/debug"
|
||||||
|
"github.com/docker/go-units"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type inspectOptions struct {
|
type inspectOptions struct {
|
||||||
@@ -26,99 +26,109 @@ type inspectOptions struct {
|
|||||||
builder string
|
builder string
|
||||||
}
|
}
|
||||||
|
|
||||||
type dinfo struct {
|
|
||||||
di *build.DriverInfo
|
|
||||||
info *driver.Info
|
|
||||||
platforms []specs.Platform
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
type nginfo struct {
|
|
||||||
ng *store.NodeGroup
|
|
||||||
drivers []dinfo
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
txn, release, err := getStore(dockerCli)
|
b, err := builder.New(dockerCli,
|
||||||
|
builder.WithName(in.builder),
|
||||||
|
builder.WithSkippedValidation(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer release()
|
|
||||||
|
|
||||||
var ng *store.NodeGroup
|
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
|
|
||||||
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, 5*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.Flags) > 0 {
|
||||||
fmt.Fprintf(w, "Flags:\t%s\n", strings.Join(n.Flags, " "))
|
fmt.Fprintf(w, "Flags:\t%s\n", strings.Join(n.Flags, " "))
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.FormatInGroups(n.Platforms, ngi.drivers[i].platforms), ", "))
|
if nodes[i].Version != "" {
|
||||||
|
fmt.Fprintf(w, "Buildkit:\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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -142,50 +152,22 @@ func inspectCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
}
|
}
|
||||||
return runInspect(dockerCli, options)
|
return runInspect(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
|
||||||
}
|
}
|
||||||
|
|||||||
150
commands/ls.go
150
commands/ls.go
@@ -4,12 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/builder"
|
||||||
|
"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"
|
||||||
@@ -24,51 +26,30 @@ type lsOptions struct {
|
|||||||
func runLs(dockerCli command.Cli, in lsOptions) error {
|
func runLs(dockerCli command.Cli, in lsOptions) error {
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
txn, release, err := getStore(dockerCli)
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(ctx, 7*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,61 +59,62 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
currentName := "default"
|
w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
|
||||||
current, err := getCurrentInstance(txn, dockerCli)
|
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tBUILDKIT\tPLATFORMS\n")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if current != nil {
|
|
||||||
currentName = current.Name
|
|
||||||
if current.Name == "default" {
|
|
||||||
currentName = current.Nodes[0].Endpoint
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
printErr := false
|
||||||
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tPLATFORMS\n")
|
|
||||||
|
|
||||||
currentSet := false
|
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
if !currentSet && b.ng.Name == currentName {
|
if current.Name == b.Name {
|
||||||
b.ng.Name += " *"
|
b.Name += " *"
|
||||||
currentSet = true
|
}
|
||||||
|
if ok := printBuilder(w, b); !ok {
|
||||||
|
printErr = true
|
||||||
}
|
}
|
||||||
printngi(w, b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
|
|
||||||
return nil
|
if printErr {
|
||||||
}
|
_, _ = fmt.Fprintf(dockerCli.Err(), "\n")
|
||||||
|
for _, b := range builders {
|
||||||
func printngi(w io.Writer, ngi *nginfo) {
|
if b.Err() != nil {
|
||||||
var err string
|
_, _ = fmt.Fprintf(dockerCli.Err(), "Cannot load builder %s: %s\n", b.Name, strings.TrimSpace(b.Err().Error()))
|
||||||
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 {
|
} 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), ", "))
|
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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printBuilder(w io.Writer, b *builder.Builder) (ok bool) {
|
||||||
|
ok = true
|
||||||
|
var err string
|
||||||
|
if b.Err() != nil {
|
||||||
|
ok = false
|
||||||
|
err = "error"
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "%s\t%s\t%s\t\t\n", b.Name, b.Driver, err)
|
||||||
|
if b.Err() == nil {
|
||||||
|
for _, n := range b.Nodes() {
|
||||||
|
var status string
|
||||||
|
if n.DriverInfo != nil {
|
||||||
|
status = n.DriverInfo.Status.String()
|
||||||
|
}
|
||||||
|
if n.Err != nil {
|
||||||
|
ok = false
|
||||||
|
fmt.Fprintf(w, " %s\t%s\t%s\t\t\n", n.Name, n.Endpoint, "error")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, n.Version, strings.Join(platformutil.FormatInGroups(n.Node.Platforms, n.Platforms), ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func lsCmd(dockerCli command.Cli) *cobra.Command {
|
func lsCmd(dockerCli command.Cli) *cobra.Command {
|
||||||
@@ -145,7 +127,11 @@ func lsCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runLs(dockerCli, options)
|
return runLs(dockerCli, options)
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: completion.Disable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide builder persistent flag for this command
|
||||||
|
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,16 +7,17 @@ import (
|
|||||||
"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/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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -54,14 +55,18 @@ func runPrune(dockerCli command.Cli, opts pruneOptions) error {
|
|||||||
return nil
|
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 +95,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 +114,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 +124,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,21 +134,21 @@ 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(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.BoolVar(&options.force, "force", false, "Skip the warning messages")
|
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -156,9 +161,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 +174,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 +202,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
|
||||||
}
|
}
|
||||||
|
|||||||
145
commands/rm.go
145
commands/rm.go
@@ -2,51 +2,77 @@ 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/buildx/store/storeutil"
|
||||||
|
"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/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
|
builder string
|
||||||
|
keepState bool
|
||||||
|
keepDaemon bool
|
||||||
|
allInactive bool
|
||||||
|
force bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
rmInactiveWarning = `WARNING! This will remove all builders that are not in running state. Are you sure you want to continue?`
|
||||||
|
)
|
||||||
|
|
||||||
func runRm(dockerCli command.Cli, in rmOptions) error {
|
func runRm(dockerCli command.Cli, in rmOptions) error {
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
txn, release, err := getStore(dockerCli)
|
if in.allInactive && !in.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), rmInactiveWarning) {
|
||||||
|
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)
|
b, err := builder.New(dockerCli,
|
||||||
|
builder.WithName(in.builder),
|
||||||
|
builder.WithStore(txn),
|
||||||
|
builder.WithSkippedValidation(),
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if ng != nil {
|
|
||||||
err1 := stop(ctx, dockerCli, ng, true)
|
nodes, err := b.LoadNodes(ctx)
|
||||||
if err := txn.Remove(ng.Name); err != nil {
|
if err != nil {
|
||||||
return err
|
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 err1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,57 +86,78 @@ func rmCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.builder = rootOpts.builder
|
options.builder = rootOpts.builder
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
|
if options.allInactive {
|
||||||
|
return errors.New("cannot specify builder name when --all-inactive is set")
|
||||||
|
}
|
||||||
options.builder = args[0]
|
options.builder = args[0]
|
||||||
}
|
}
|
||||||
return runRm(dockerCli, options)
|
return runRm(dockerCli, options)
|
||||||
},
|
},
|
||||||
|
ValidArgsFunction: completion.BuilderNames(dockerCli),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.BoolVar(&options.keepState, "keep-state", false, "Keep BuildKit state")
|
||||||
|
flags.BoolVar(&options.keepDaemon, "keep-daemon", false, "Keep the buildkitd daemon running")
|
||||||
|
flags.BoolVar(&options.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,62 @@ 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/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/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{
|
||||||
|
annotation.CodeDelimiter: `"`,
|
||||||
|
},
|
||||||
|
CompletionOptions: cobra.CompletionOptions{
|
||||||
|
HiddenDefaultCmd: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if isPlugin {
|
if isPlugin {
|
||||||
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
return plugin.PersistentPreRunE(cmd, args)
|
return plugin.PersistentPreRunE(cmd, args)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 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",
|
||||||
|
))
|
||||||
|
|
||||||
addCommands(cmd, dockerCli)
|
addCommands(cmd, dockerCli)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -34,7 +72,7 @@ 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),
|
||||||
rmCmd(dockerCli, opts),
|
rmCmd(dockerCli, opts),
|
||||||
@@ -47,7 +85,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 isExperimental() {
|
||||||
|
cmd.AddCommand(debugcmd.RootCmd(dockerCli,
|
||||||
|
newDebuggableBuild(dockerCli, opts),
|
||||||
|
))
|
||||||
|
remote.AddControllerCommands(cmd, dockerCli)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.RegisterFlagCompletionFunc( //nolint:errcheck
|
||||||
|
"builder",
|
||||||
|
completion.BuilderNames(dockerCli),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
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/moby/buildkit/util/appcontext"
|
||||||
@@ -14,32 +18,19 @@ type stopOptions struct {
|
|||||||
func runStop(dockerCli command.Cli, in stopOptions) error {
|
func runStop(dockerCli command.Cli, in stopOptions) error {
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
txn, release, err := getStore(dockerCli)
|
b, err := builder.New(dockerCli,
|
||||||
|
builder.WithName(in.builder),
|
||||||
|
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 {
|
||||||
@@ -56,13 +47,22 @@ func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
}
|
}
|
||||||
return runStop(dockerCli, options)
|
return runStop(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
|
||||||
}
|
}
|
||||||
|
|||||||
388
commands/util.go
388
commands/util.go
@@ -1,388 +0,0 @@
|
|||||||
package commands
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
|
||||||
"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 getStore(dockerCli command.Cli) (*store.Txn, func(), error) {
|
|
||||||
dir := filepath.Dir(dockerCli.ConfigFile().Filename)
|
|
||||||
s, err := store.New(dir)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return s.Txn()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getCurrentEndpoint returns the current default endpoint value
|
|
||||||
func getCurrentEndpoint(dockerCli command.Cli) (string, error) {
|
|
||||||
name := dockerCli.CurrentContext()
|
|
||||||
if name != "default" {
|
|
||||||
return name, nil
|
|
||||||
}
|
|
||||||
de, err := getDockerEndpoint(dockerCli, name)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Errorf("docker endpoint for %q not found", name)
|
|
||||||
}
|
|
||||||
return de, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDockerEndpoint returns docker endpoint string for given context
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
280
controller/build/build.go
Normal file
280
controller/build/build.go
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if in.ExportLoad {
|
||||||
|
return nil, nil, errors.Errorf("push and load may not be set together at the moment")
|
||||||
|
}
|
||||||
|
if len(outputs) == 0 {
|
||||||
|
outputs = []client.ExportEntry{{
|
||||||
|
Type: "image",
|
||||||
|
Attrs: map[string]string{
|
||||||
|
"push": "true",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
} else {
|
||||||
|
switch outputs[0].Type {
|
||||||
|
case "image":
|
||||||
|
outputs[0].Attrs["push"] = "true"
|
||||||
|
default:
|
||||||
|
return nil, nil, errors.Errorf("push and %q output can't be used together", outputs[0].Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in.ExportLoad {
|
||||||
|
if len(outputs) == 0 {
|
||||||
|
outputs = []client.ExportEntry{{
|
||||||
|
Type: "docker",
|
||||||
|
Attrs: map[string]string{},
|
||||||
|
}}
|
||||||
|
} else {
|
||||||
|
switch outputs[0].Type {
|
||||||
|
case "docker":
|
||||||
|
default:
|
||||||
|
return nil, nil, errors.Errorf("load and %q output can't be used together", outputs[0].Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
2701
controller/pb/controller.pb.go
Normal file
2701
controller/pb/controller.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
248
controller/pb/controller.proto
Normal file
248
controller/pb/controller.proto
Normal file
@@ -0,0 +1,248 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
100
controller/pb/export.go
Normal file
100
controller/pb/export.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
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 _, 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)
|
||||||
|
} 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)
|
||||||
|
}
|
||||||
149
controller/processes/processes.go
Normal file
149
controller/processes/processes.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package processes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/build"
|
||||||
|
"github.com/docker/buildx/controller/pb"
|
||||||
|
"github.com/docker/buildx/util/ioset"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Process provides methods to control a process.
|
||||||
|
type Process struct {
|
||||||
|
inEnd *ioset.Forwarder
|
||||||
|
invokeConfig *pb.InvokeConfig
|
||||||
|
errCh chan error
|
||||||
|
processCancel func()
|
||||||
|
serveIOCancel func()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForwardIO forwards process's io to the specified reader/writer.
|
||||||
|
// Optionally specify ioCancelCallback which will be called when
|
||||||
|
// the process closes the specified IO. This will be useful for additional cleanup.
|
||||||
|
func (p *Process) ForwardIO(in *ioset.In, ioCancelCallback func()) {
|
||||||
|
p.inEnd.SetIn(in)
|
||||||
|
if f := p.serveIOCancel; f != nil {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
p.serveIOCancel = ioCancelCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done returns a channel where error or nil will be sent
|
||||||
|
// when the process exits.
|
||||||
|
// TODO: change this to Wait()
|
||||||
|
func (p *Process) Done() <-chan error {
|
||||||
|
return p.errCh
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager manages a set of proceses.
|
||||||
|
type Manager struct {
|
||||||
|
container atomic.Value
|
||||||
|
processes sync.Map
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager creates and returns a Manager.
|
||||||
|
func NewManager() *Manager {
|
||||||
|
return &Manager{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the specified process.
|
||||||
|
func (m *Manager) Get(id string) (*Process, bool) {
|
||||||
|
v, ok := m.processes.Load(id)
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
return v.(*Process), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelRunningProcesses cancels execution of all running processes.
|
||||||
|
func (m *Manager) CancelRunningProcesses() {
|
||||||
|
var funcs []func()
|
||||||
|
m.processes.Range(func(key, value any) bool {
|
||||||
|
funcs = append(funcs, value.(*Process).processCancel)
|
||||||
|
m.processes.Delete(key)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
for _, f := range funcs {
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListProcesses lists all running processes.
|
||||||
|
func (m *Manager) ListProcesses() (res []*pb.ProcessInfo) {
|
||||||
|
m.processes.Range(func(key, value any) bool {
|
||||||
|
res = append(res, &pb.ProcessInfo{
|
||||||
|
ProcessID: key.(string),
|
||||||
|
InvokeConfig: value.(*Process).invokeConfig,
|
||||||
|
})
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteProcess deletes the specified process.
|
||||||
|
func (m *Manager) DeleteProcess(id string) error {
|
||||||
|
p, ok := m.processes.LoadAndDelete(id)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("unknown process %q", id)
|
||||||
|
}
|
||||||
|
p.(*Process).processCancel()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartProcess starts a process in the container.
|
||||||
|
// When a container isn't available (i.e. first time invoking or the container has exited) or cfg.Rollback is set,
|
||||||
|
// this method will start a new container and run the process in it. Otherwise, this method starts a new process in the
|
||||||
|
// existing container.
|
||||||
|
func (m *Manager) StartProcess(pid string, resultCtx *build.ResultHandle, cfg *pb.InvokeConfig) (*Process, error) {
|
||||||
|
// Get the target result to invoke a container from
|
||||||
|
var ctr *build.Container
|
||||||
|
if a := m.container.Load(); a != nil {
|
||||||
|
ctr = a.(*build.Container)
|
||||||
|
}
|
||||||
|
if cfg.Rollback || ctr == nil || ctr.IsUnavailable() {
|
||||||
|
go m.CancelRunningProcesses()
|
||||||
|
// (Re)create a new container if this is rollback or first time to invoke a process.
|
||||||
|
if ctr != nil {
|
||||||
|
go ctr.Cancel() // Finish the existing container
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
ctr, err = build.NewContainer(context.TODO(), resultCtx, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Errorf("failed to create container %v", err)
|
||||||
|
}
|
||||||
|
m.container.Store(ctr)
|
||||||
|
}
|
||||||
|
// [client(ForwardIO)] <-forwarder(switchable)-> [out] <-pipe-> [in] <- [process]
|
||||||
|
in, out := ioset.Pipe()
|
||||||
|
f := ioset.NewForwarder()
|
||||||
|
f.PropagateStdinClose = false
|
||||||
|
f.SetOut(&out)
|
||||||
|
|
||||||
|
// Register process
|
||||||
|
ctx, cancel := context.WithCancel(context.TODO())
|
||||||
|
var cancelOnce sync.Once
|
||||||
|
processCancelFunc := func() { cancelOnce.Do(func() { cancel(); f.Close(); in.Close(); out.Close() }) }
|
||||||
|
p := &Process{
|
||||||
|
inEnd: f,
|
||||||
|
invokeConfig: cfg,
|
||||||
|
processCancel: processCancelFunc,
|
||||||
|
errCh: make(chan error),
|
||||||
|
}
|
||||||
|
m.processes.Store(pid, p)
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
if err = ctr.Exec(ctx, cfg, in.Stdin, in.Stdout, in.Stderr); err != nil {
|
||||||
|
logrus.Debugf("process error: %v", err)
|
||||||
|
}
|
||||||
|
logrus.Debugf("finished process %s %v", pid, cfg.Entrypoint)
|
||||||
|
m.processes.Delete(pid)
|
||||||
|
processCancelFunc()
|
||||||
|
p.errCh <- err
|
||||||
|
}()
|
||||||
|
|
||||||
|
return p, nil
|
||||||
|
}
|
||||||
240
controller/remote/client.go
Normal file
240
controller/remote/client.go
Normal file
@@ -0,0 +1,240 @@
|
|||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/defaults"
|
||||||
|
"github.com/containerd/containerd/pkg/dialer"
|
||||||
|
"github.com/docker/buildx/controller/pb"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/identity"
|
||||||
|
"github.com/moby/buildkit/util/grpcerrors"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/backoff"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewClient(ctx context.Context, addr string) (*Client, error) {
|
||||||
|
backoffConfig := backoff.DefaultConfig
|
||||||
|
backoffConfig.MaxDelay = 3 * time.Second
|
||||||
|
connParams := grpc.ConnectParams{
|
||||||
|
Backoff: backoffConfig,
|
||||||
|
}
|
||||||
|
gopts := []grpc.DialOption{
|
||||||
|
grpc.WithBlock(),
|
||||||
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||||
|
grpc.WithConnectParams(connParams),
|
||||||
|
grpc.WithContextDialer(dialer.ContextDialer),
|
||||||
|
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(defaults.DefaultMaxRecvMsgSize)),
|
||||||
|
grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(defaults.DefaultMaxSendMsgSize)),
|
||||||
|
grpc.WithUnaryInterceptor(grpcerrors.UnaryClientInterceptor),
|
||||||
|
grpc.WithStreamInterceptor(grpcerrors.StreamClientInterceptor),
|
||||||
|
}
|
||||||
|
conn, err := grpc.DialContext(ctx, dialer.DialAddress(addr), gopts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Client{conn: conn}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
conn *grpc.ClientConn
|
||||||
|
closeOnce sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Close() (err error) {
|
||||||
|
c.closeOnce.Do(func() {
|
||||||
|
err = c.conn.Close()
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Version(ctx context.Context) (string, string, string, error) {
|
||||||
|
res, err := c.client().Info(ctx, &pb.InfoRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
v := res.BuildxVersion
|
||||||
|
return v.Package, v.Version, v.Revision, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) List(ctx context.Context) (keys []string, retErr error) {
|
||||||
|
res, err := c.client().List(ctx, &pb.ListRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res.Keys, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Disconnect(ctx context.Context, key string) error {
|
||||||
|
if key == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
_, err := c.client().Disconnect(ctx, &pb.DisconnectRequest{Ref: key})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListProcesses(ctx context.Context, ref string) (infos []*pb.ProcessInfo, retErr error) {
|
||||||
|
res, err := c.client().ListProcesses(ctx, &pb.ListProcessesRequest{Ref: ref})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return res.Infos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DisconnectProcess(ctx context.Context, ref, pid string) error {
|
||||||
|
_, err := c.client().DisconnectProcess(ctx, &pb.DisconnectProcessRequest{Ref: ref, ProcessID: pid})
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Invoke(ctx context.Context, ref string, pid string, invokeConfig pb.InvokeConfig, in io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser) error {
|
||||||
|
if ref == "" || pid == "" {
|
||||||
|
return errors.New("build reference must be specified")
|
||||||
|
}
|
||||||
|
stream, err := c.client().Invoke(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return attachIO(ctx, stream, &pb.InitMessage{Ref: ref, ProcessID: pid, InvokeConfig: &invokeConfig}, ioAttachConfig{
|
||||||
|
stdin: in,
|
||||||
|
stdout: stdout,
|
||||||
|
stderr: stderr,
|
||||||
|
// TODO: Signal, Resize
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Inspect(ctx context.Context, ref string) (*pb.InspectResponse, error) {
|
||||||
|
return c.client().Inspect(ctx, &pb.InspectRequest{Ref: ref})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Build(ctx context.Context, options pb.BuildOptions, in io.ReadCloser, progress progress.Writer) (string, *client.SolveResponse, error) {
|
||||||
|
ref := identity.NewID()
|
||||||
|
statusChan := make(chan *client.SolveStatus)
|
||||||
|
eg, egCtx := errgroup.WithContext(ctx)
|
||||||
|
var resp *client.SolveResponse
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer close(statusChan)
|
||||||
|
var err error
|
||||||
|
resp, err = c.build(egCtx, ref, options, in, statusChan)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
eg.Go(func() error {
|
||||||
|
for s := range statusChan {
|
||||||
|
st := s
|
||||||
|
progress.Write(st)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return ref, resp, eg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) build(ctx context.Context, ref string, options pb.BuildOptions, in io.ReadCloser, statusChan chan *client.SolveStatus) (*client.SolveResponse, error) {
|
||||||
|
eg, egCtx := errgroup.WithContext(ctx)
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
var resp *client.SolveResponse
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer close(done)
|
||||||
|
pbResp, err := c.client().Build(egCtx, &pb.BuildRequest{
|
||||||
|
Ref: ref,
|
||||||
|
Options: &options,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp = &client.SolveResponse{
|
||||||
|
ExporterResponse: pbResp.ExporterResponse,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
eg.Go(func() error {
|
||||||
|
stream, err := c.client().Status(egCtx, &pb.StatusRequest{
|
||||||
|
Ref: ref,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
resp, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Wrap(err, "failed to receive status")
|
||||||
|
}
|
||||||
|
statusChan <- pb.FromControlStatus(resp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if in != nil {
|
||||||
|
eg.Go(func() error {
|
||||||
|
stream, err := c.client().Input(egCtx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := stream.Send(&pb.InputMessage{
|
||||||
|
Input: &pb.InputMessage_Init{
|
||||||
|
Init: &pb.InputInitMessage{
|
||||||
|
Ref: ref,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to init input")
|
||||||
|
}
|
||||||
|
|
||||||
|
inReader, inWriter := io.Pipe()
|
||||||
|
eg2, _ := errgroup.WithContext(ctx)
|
||||||
|
eg2.Go(func() error {
|
||||||
|
<-done
|
||||||
|
return inWriter.Close()
|
||||||
|
})
|
||||||
|
go func() {
|
||||||
|
// do not wait for read completion but return here and let the caller send EOF
|
||||||
|
// this allows us to return on ctx.Done() without being blocked by this reader.
|
||||||
|
io.Copy(inWriter, in)
|
||||||
|
inWriter.Close()
|
||||||
|
}()
|
||||||
|
eg2.Go(func() error {
|
||||||
|
for {
|
||||||
|
buf := make([]byte, 32*1024)
|
||||||
|
n, err := inReader.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break // break loop and send EOF
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
} else if n > 0 {
|
||||||
|
if stream.Send(&pb.InputMessage{
|
||||||
|
Input: &pb.InputMessage_Data{
|
||||||
|
Data: &pb.DataMessage{
|
||||||
|
Data: buf[:n],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return stream.Send(&pb.InputMessage{
|
||||||
|
Input: &pb.InputMessage_Data{
|
||||||
|
Data: &pb.DataMessage{
|
||||||
|
EOF: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return eg2.Wait()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return resp, eg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) client() pb.ControllerClient {
|
||||||
|
return pb.NewControllerClient(c.conn)
|
||||||
|
}
|
||||||
333
controller/remote/controller.go
Normal file
333
controller/remote/controller.go
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
//go:build linux
|
||||||
|
|
||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/containerd/log"
|
||||||
|
"github.com/docker/buildx/build"
|
||||||
|
cbuild "github.com/docker/buildx/controller/build"
|
||||||
|
"github.com/docker/buildx/controller/control"
|
||||||
|
controllerapi "github.com/docker/buildx/controller/pb"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/buildx/version"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/util/grpcerrors"
|
||||||
|
"github.com/pelletier/go-toml"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
serveCommandName = "_INTERNAL_SERVE"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultLogFilename = fmt.Sprintf("buildx.%s.log", version.Revision)
|
||||||
|
defaultSocketFilename = fmt.Sprintf("buildx.%s.sock", version.Revision)
|
||||||
|
defaultPIDFilename = fmt.Sprintf("buildx.%s.pid", version.Revision)
|
||||||
|
)
|
||||||
|
|
||||||
|
type serverConfig struct {
|
||||||
|
// Specify buildx server root
|
||||||
|
Root string `toml:"root"`
|
||||||
|
|
||||||
|
// LogLevel sets the logging level [trace, debug, info, warn, error, fatal, panic]
|
||||||
|
LogLevel string `toml:"log_level"`
|
||||||
|
|
||||||
|
// Specify file to output buildx server log
|
||||||
|
LogFile string `toml:"log_file"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRemoteBuildxController(ctx context.Context, dockerCli command.Cli, opts control.ControlOptions, logger progress.SubLogger) (control.BuildxController, error) {
|
||||||
|
rootDir := opts.Root
|
||||||
|
if rootDir == "" {
|
||||||
|
rootDir = rootDataDir(dockerCli)
|
||||||
|
}
|
||||||
|
serverRoot := filepath.Join(rootDir, "shared")
|
||||||
|
|
||||||
|
// connect to buildx server if it is already running
|
||||||
|
ctx2, cancel := context.WithTimeout(ctx, 1*time.Second)
|
||||||
|
c, err := newBuildxClientAndCheck(ctx2, filepath.Join(serverRoot, defaultSocketFilename))
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, context.DeadlineExceeded) {
|
||||||
|
return nil, errors.Wrap(err, "cannot connect to the buildx server")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return &buildxController{c, serverRoot}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// start buildx server via subcommand
|
||||||
|
err = logger.Wrap("no buildx server found; launching...", func() error {
|
||||||
|
launchFlags := []string{}
|
||||||
|
if opts.ServerConfig != "" {
|
||||||
|
launchFlags = append(launchFlags, "--config", opts.ServerConfig)
|
||||||
|
}
|
||||||
|
logFile, err := getLogFilePath(dockerCli, opts.ServerConfig)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wait, err := launch(ctx, logFile, append([]string{serveCommandName}, launchFlags...)...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go wait()
|
||||||
|
|
||||||
|
// wait for buildx server to be ready
|
||||||
|
ctx2, cancel = context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
c, err = newBuildxClientAndCheck(ctx2, filepath.Join(serverRoot, defaultSocketFilename))
|
||||||
|
cancel()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "cannot connect to the buildx server")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &buildxController{c, serverRoot}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddControllerCommands(cmd *cobra.Command, dockerCli command.Cli) {
|
||||||
|
cmd.AddCommand(
|
||||||
|
serveCmd(dockerCli),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveCmd(dockerCli command.Cli) *cobra.Command {
|
||||||
|
var serverConfigPath string
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: fmt.Sprintf("%s [OPTIONS]", serveCommandName),
|
||||||
|
Hidden: true,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
// Parse config
|
||||||
|
config, err := getConfig(dockerCli, serverConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if config.LogLevel == "" {
|
||||||
|
logrus.SetLevel(logrus.InfoLevel)
|
||||||
|
} else {
|
||||||
|
lvl, err := logrus.ParseLevel(config.LogLevel)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to prepare logger")
|
||||||
|
}
|
||||||
|
logrus.SetLevel(lvl)
|
||||||
|
}
|
||||||
|
logrus.SetFormatter(&logrus.JSONFormatter{
|
||||||
|
TimestampFormat: log.RFC3339NanoFixed,
|
||||||
|
})
|
||||||
|
root, err := prepareRootDir(dockerCli, config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pidF := filepath.Join(root, defaultPIDFilename)
|
||||||
|
if err := os.WriteFile(pidF, []byte(fmt.Sprintf("%d", os.Getpid())), 0600); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := os.Remove(pidF); err != nil {
|
||||||
|
logrus.Errorf("failed to clean up info file %q: %v", pidF, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// prepare server
|
||||||
|
b := NewServer(func(ctx context.Context, options *controllerapi.BuildOptions, stdin io.Reader, progress progress.Writer) (*client.SolveResponse, *build.ResultHandle, error) {
|
||||||
|
return cbuild.RunBuild(ctx, dockerCli, *options, stdin, progress, true)
|
||||||
|
})
|
||||||
|
defer b.Close()
|
||||||
|
|
||||||
|
// serve server
|
||||||
|
addr := filepath.Join(root, defaultSocketFilename)
|
||||||
|
if err := os.Remove(addr); err != nil && !os.IsNotExist(err) { // avoid EADDRINUSE
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := os.Remove(addr); err != nil {
|
||||||
|
logrus.Errorf("failed to clean up socket %q: %v", addr, err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
logrus.Infof("starting server at %q", addr)
|
||||||
|
l, err := net.Listen("unix", addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rpc := grpc.NewServer(
|
||||||
|
grpc.UnaryInterceptor(grpcerrors.UnaryServerInterceptor),
|
||||||
|
grpc.StreamInterceptor(grpcerrors.StreamServerInterceptor),
|
||||||
|
)
|
||||||
|
controllerapi.RegisterControllerServer(rpc, b)
|
||||||
|
doneCh := make(chan struct{})
|
||||||
|
errCh := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
defer close(doneCh)
|
||||||
|
if err := rpc.Serve(l); err != nil {
|
||||||
|
errCh <- errors.Wrapf(err, "error on serving via socket %q", addr)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var s os.Signal
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, syscall.SIGINT)
|
||||||
|
signal.Notify(sigCh, syscall.SIGTERM)
|
||||||
|
select {
|
||||||
|
case err := <-errCh:
|
||||||
|
logrus.Errorf("got error %s, exiting", err)
|
||||||
|
return err
|
||||||
|
case s = <-sigCh:
|
||||||
|
logrus.Infof("got signal %s, exiting", s)
|
||||||
|
return nil
|
||||||
|
case <-doneCh:
|
||||||
|
logrus.Infof("rpc server done, exiting")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
flags := cmd.Flags()
|
||||||
|
flags.StringVar(&serverConfigPath, "config", "", "Specify buildx server config file")
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLogFilePath(dockerCli command.Cli, configPath string) (string, error) {
|
||||||
|
config, err := getConfig(dockerCli, configPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if config.LogFile == "" {
|
||||||
|
root, err := prepareRootDir(dockerCli, config)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(root, defaultLogFilename), nil
|
||||||
|
}
|
||||||
|
return config.LogFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfig(dockerCli command.Cli, configPath string) (*serverConfig, error) {
|
||||||
|
var defaultConfigPath bool
|
||||||
|
if configPath == "" {
|
||||||
|
defaultRoot := rootDataDir(dockerCli)
|
||||||
|
configPath = filepath.Join(defaultRoot, "config.toml")
|
||||||
|
defaultConfigPath = true
|
||||||
|
}
|
||||||
|
var config serverConfig
|
||||||
|
tree, err := toml.LoadFile(configPath)
|
||||||
|
if err != nil && !(os.IsNotExist(err) && defaultConfigPath) {
|
||||||
|
return nil, errors.Wrapf(err, "failed to read config %q", configPath)
|
||||||
|
} else if err == nil {
|
||||||
|
if err := tree.Unmarshal(&config); err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "failed to unmarshal config %q", configPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareRootDir(dockerCli command.Cli, config *serverConfig) (string, error) {
|
||||||
|
rootDir := config.Root
|
||||||
|
if rootDir == "" {
|
||||||
|
rootDir = rootDataDir(dockerCli)
|
||||||
|
}
|
||||||
|
if rootDir == "" {
|
||||||
|
return "", errors.New("buildx root dir must be determined")
|
||||||
|
}
|
||||||
|
if err := os.MkdirAll(rootDir, 0700); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
serverRoot := filepath.Join(rootDir, "shared")
|
||||||
|
if err := os.MkdirAll(serverRoot, 0700); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return serverRoot, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rootDataDir(dockerCli command.Cli) string {
|
||||||
|
return filepath.Join(confutil.ConfigDir(dockerCli), "controller")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuildxClientAndCheck(ctx context.Context, addr string) (*Client, error) {
|
||||||
|
c, err := NewClient(ctx, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p, v, r, err := c.Version(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
logrus.Debugf("connected to server (\"%v %v %v\")", p, v, r)
|
||||||
|
if !(p == version.Package && v == version.Version && r == version.Revision) {
|
||||||
|
return nil, errors.Errorf("version mismatch (client: \"%v %v %v\", server: \"%v %v %v\")", version.Package, version.Version, version.Revision, p, v, r)
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type buildxController struct {
|
||||||
|
*Client
|
||||||
|
serverRoot string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *buildxController) Kill(ctx context.Context) error {
|
||||||
|
pidB, err := os.ReadFile(filepath.Join(c.serverRoot, defaultPIDFilename))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pid, err := strconv.ParseInt(string(pidB), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pid <= 0 {
|
||||||
|
return errors.New("no PID is recorded for buildx server")
|
||||||
|
}
|
||||||
|
p, err := os.FindProcess(int(pid))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.Signal(syscall.SIGINT); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// TODO: Should we send SIGKILL if process doesn't finish?
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func launch(ctx context.Context, logFile string, args ...string) (func() error, error) {
|
||||||
|
// set absolute path of binary, since we set the working directory to the root
|
||||||
|
pathname, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bCmd := exec.CommandContext(ctx, pathname, args...)
|
||||||
|
if logFile != "" {
|
||||||
|
f, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
bCmd.Stdout = f
|
||||||
|
bCmd.Stderr = f
|
||||||
|
}
|
||||||
|
bCmd.Stdin = nil
|
||||||
|
bCmd.Dir = "/"
|
||||||
|
bCmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
Setsid: true,
|
||||||
|
}
|
||||||
|
if err := bCmd.Start(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return bCmd.Wait, nil
|
||||||
|
}
|
||||||
19
controller/remote/controller_nolinux.go
Normal file
19
controller/remote/controller_nolinux.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build !linux
|
||||||
|
|
||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/controller/control"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewRemoteBuildxController(ctx context.Context, dockerCli command.Cli, opts control.ControlOptions, logger progress.SubLogger) (control.BuildxController, error) {
|
||||||
|
return nil, errors.New("remote buildx unsupported")
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddControllerCommands(cmd *cobra.Command, dockerCli command.Cli) {}
|
||||||
430
controller/remote/io.go
Normal file
430
controller/remote/io.go
Normal file
@@ -0,0 +1,430 @@
|
|||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/controller/pb"
|
||||||
|
"github.com/moby/sys/signal"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type msgStream interface {
|
||||||
|
Send(*pb.Message) error
|
||||||
|
Recv() (*pb.Message, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ioServerConfig struct {
|
||||||
|
stdin io.WriteCloser
|
||||||
|
stdout, stderr io.ReadCloser
|
||||||
|
|
||||||
|
// signalFn is a callback function called when a signal is reached to the client.
|
||||||
|
signalFn func(context.Context, syscall.Signal) error
|
||||||
|
|
||||||
|
// resizeFn is a callback function called when a resize event is reached to the client.
|
||||||
|
resizeFn func(context.Context, winSize) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveIO(attachCtx context.Context, srv msgStream, initFn func(*pb.InitMessage) error, ioConfig *ioServerConfig) (err error) {
|
||||||
|
stdin, stdout, stderr := ioConfig.stdin, ioConfig.stdout, ioConfig.stderr
|
||||||
|
stream := &debugStream{srv, "server=" + time.Now().String()}
|
||||||
|
eg, ctx := errgroup.WithContext(attachCtx)
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
msg, err := receive(ctx, stream)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
init := msg.GetInit()
|
||||||
|
if init == nil {
|
||||||
|
return errors.Errorf("unexpected message: %T; wanted init", msg.GetInput())
|
||||||
|
}
|
||||||
|
ref := init.Ref
|
||||||
|
if ref == "" {
|
||||||
|
return errors.New("no ref is provided")
|
||||||
|
}
|
||||||
|
if err := initFn(init); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to initialize IO server")
|
||||||
|
}
|
||||||
|
|
||||||
|
if stdout != nil {
|
||||||
|
stdoutReader, stdoutWriter := io.Pipe()
|
||||||
|
eg.Go(func() error {
|
||||||
|
<-done
|
||||||
|
return stdoutWriter.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// do not wait for read completion but return here and let the caller send EOF
|
||||||
|
// this allows us to return on ctx.Done() without being blocked by this reader.
|
||||||
|
io.Copy(stdoutWriter, stdout)
|
||||||
|
stdoutWriter.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer stdoutReader.Close()
|
||||||
|
return copyToStream(1, stream, stdoutReader)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if stderr != nil {
|
||||||
|
stderrReader, stderrWriter := io.Pipe()
|
||||||
|
eg.Go(func() error {
|
||||||
|
<-done
|
||||||
|
return stderrWriter.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// do not wait for read completion but return here and let the caller send EOF
|
||||||
|
// this allows us to return on ctx.Done() without being blocked by this reader.
|
||||||
|
io.Copy(stderrWriter, stderr)
|
||||||
|
stderrWriter.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer stderrReader.Close()
|
||||||
|
return copyToStream(2, stream, stderrReader)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
msgCh := make(chan *pb.Message)
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer close(msgCh)
|
||||||
|
for {
|
||||||
|
msg, err := receive(ctx, stream)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case msgCh <- msg:
|
||||||
|
case <-done:
|
||||||
|
return nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer close(done)
|
||||||
|
for {
|
||||||
|
var msg *pb.Message
|
||||||
|
select {
|
||||||
|
case msg = <-msgCh:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if msg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if file := msg.GetFile(); file != nil {
|
||||||
|
if file.Fd != 0 {
|
||||||
|
return errors.Errorf("unexpected fd: %v", file.Fd)
|
||||||
|
}
|
||||||
|
if stdin == nil {
|
||||||
|
continue // no stdin destination is specified so ignore the data
|
||||||
|
}
|
||||||
|
if len(file.Data) > 0 {
|
||||||
|
_, err := stdin.Write(file.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if file.EOF {
|
||||||
|
stdin.Close()
|
||||||
|
}
|
||||||
|
} else if resize := msg.GetResize(); resize != nil {
|
||||||
|
if ioConfig.resizeFn != nil {
|
||||||
|
ioConfig.resizeFn(ctx, winSize{
|
||||||
|
cols: resize.Cols,
|
||||||
|
rows: resize.Rows,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if sig := msg.GetSignal(); sig != nil {
|
||||||
|
if ioConfig.signalFn != nil {
|
||||||
|
syscallSignal, ok := signal.SignalMap[sig.Name]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ioConfig.signalFn(ctx, syscallSignal)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.Errorf("unexpected message: %T", msg.GetInput())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return eg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
type ioAttachConfig struct {
|
||||||
|
stdin io.ReadCloser
|
||||||
|
stdout, stderr io.WriteCloser
|
||||||
|
signal <-chan syscall.Signal
|
||||||
|
resize <-chan winSize
|
||||||
|
}
|
||||||
|
|
||||||
|
type winSize struct {
|
||||||
|
rows uint32
|
||||||
|
cols uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func attachIO(ctx context.Context, stream msgStream, initMessage *pb.InitMessage, cfg ioAttachConfig) (retErr error) {
|
||||||
|
eg, ctx := errgroup.WithContext(ctx)
|
||||||
|
done := make(chan struct{})
|
||||||
|
|
||||||
|
if err := stream.Send(&pb.Message{
|
||||||
|
Input: &pb.Message_Init{
|
||||||
|
Init: initMessage,
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to init")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.stdin != nil {
|
||||||
|
stdinReader, stdinWriter := io.Pipe()
|
||||||
|
eg.Go(func() error {
|
||||||
|
<-done
|
||||||
|
return stdinWriter.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// do not wait for read completion but return here and let the caller send EOF
|
||||||
|
// this allows us to return on ctx.Done() without being blocked by this reader.
|
||||||
|
io.Copy(stdinWriter, cfg.stdin)
|
||||||
|
stdinWriter.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer stdinReader.Close()
|
||||||
|
return copyToStream(0, stream, stdinReader)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.signal != nil {
|
||||||
|
eg.Go(func() error {
|
||||||
|
for {
|
||||||
|
var sig syscall.Signal
|
||||||
|
select {
|
||||||
|
case sig = <-cfg.signal:
|
||||||
|
case <-done:
|
||||||
|
return nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
name := sigToName[sig]
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := stream.Send(&pb.Message{
|
||||||
|
Input: &pb.Message_Signal{
|
||||||
|
Signal: &pb.SignalMessage{
|
||||||
|
Name: name,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to send signal")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.resize != nil {
|
||||||
|
eg.Go(func() error {
|
||||||
|
for {
|
||||||
|
var win winSize
|
||||||
|
select {
|
||||||
|
case win = <-cfg.resize:
|
||||||
|
case <-done:
|
||||||
|
return nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := stream.Send(&pb.Message{
|
||||||
|
Input: &pb.Message_Resize{
|
||||||
|
Resize: &pb.ResizeMessage{
|
||||||
|
Rows: win.rows,
|
||||||
|
Cols: win.cols,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to send resize")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
msgCh := make(chan *pb.Message)
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer close(msgCh)
|
||||||
|
for {
|
||||||
|
msg, err := receive(ctx, stream)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case msgCh <- msg:
|
||||||
|
case <-done:
|
||||||
|
return nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
eg.Go(func() error {
|
||||||
|
eofs := make(map[uint32]struct{})
|
||||||
|
defer close(done)
|
||||||
|
for {
|
||||||
|
var msg *pb.Message
|
||||||
|
select {
|
||||||
|
case msg = <-msgCh:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if msg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if file := msg.GetFile(); file != nil {
|
||||||
|
if _, ok := eofs[file.Fd]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var out io.WriteCloser
|
||||||
|
switch file.Fd {
|
||||||
|
case 1:
|
||||||
|
out = cfg.stdout
|
||||||
|
case 2:
|
||||||
|
out = cfg.stderr
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unsupported fd %d", file.Fd)
|
||||||
|
|
||||||
|
}
|
||||||
|
if out == nil {
|
||||||
|
logrus.Warnf("attachIO: no writer for fd %d", file.Fd)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(file.Data) > 0 {
|
||||||
|
if _, err := out.Write(file.Data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if file.EOF {
|
||||||
|
eofs[file.Fd] = struct{}{}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return errors.Errorf("unexpected message: %T", msg.GetInput())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return eg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func receive(ctx context.Context, stream msgStream) (*pb.Message, error) {
|
||||||
|
msgCh := make(chan *pb.Message)
|
||||||
|
errCh := make(chan error)
|
||||||
|
go func() {
|
||||||
|
msg, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, io.EOF) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errCh <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
msgCh <- msg
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case msg := <-msgCh:
|
||||||
|
return msg, nil
|
||||||
|
case err := <-errCh:
|
||||||
|
return nil, err
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyToStream(fd uint32, snd msgStream, r io.Reader) error {
|
||||||
|
for {
|
||||||
|
buf := make([]byte, 32*1024)
|
||||||
|
n, err := r.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break // break loop and send EOF
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
} else if n > 0 {
|
||||||
|
if snd.Send(&pb.Message{
|
||||||
|
Input: &pb.Message_File{
|
||||||
|
File: &pb.FdMessage{
|
||||||
|
Fd: fd,
|
||||||
|
Data: buf[:n],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return snd.Send(&pb.Message{
|
||||||
|
Input: &pb.Message_File{
|
||||||
|
File: &pb.FdMessage{
|
||||||
|
Fd: fd,
|
||||||
|
EOF: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var sigToName = map[syscall.Signal]string{}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for name, value := range signal.SignalMap {
|
||||||
|
sigToName[value] = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type debugStream struct {
|
||||||
|
msgStream
|
||||||
|
prefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *debugStream) Send(msg *pb.Message) error {
|
||||||
|
switch m := msg.GetInput().(type) {
|
||||||
|
case *pb.Message_File:
|
||||||
|
if m.File.EOF {
|
||||||
|
logrus.Debugf("|---> File Message (sender:%v) fd=%d, EOF", s.prefix, m.File.Fd)
|
||||||
|
} else {
|
||||||
|
logrus.Debugf("|---> File Message (sender:%v) fd=%d, %d bytes", s.prefix, m.File.Fd, len(m.File.Data))
|
||||||
|
}
|
||||||
|
case *pb.Message_Resize:
|
||||||
|
logrus.Debugf("|---> Resize Message (sender:%v): %+v", s.prefix, m.Resize)
|
||||||
|
case *pb.Message_Signal:
|
||||||
|
logrus.Debugf("|---> Signal Message (sender:%v): %s", s.prefix, m.Signal.Name)
|
||||||
|
}
|
||||||
|
return s.msgStream.Send(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *debugStream) Recv() (*pb.Message, error) {
|
||||||
|
msg, err := s.msgStream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch m := msg.GetInput().(type) {
|
||||||
|
case *pb.Message_File:
|
||||||
|
if m.File.EOF {
|
||||||
|
logrus.Debugf("|<--- File Message (receiver:%v) fd=%d, EOF", s.prefix, m.File.Fd)
|
||||||
|
} else {
|
||||||
|
logrus.Debugf("|<--- File Message (receiver:%v) fd=%d, %d bytes", s.prefix, m.File.Fd, len(m.File.Data))
|
||||||
|
}
|
||||||
|
case *pb.Message_Resize:
|
||||||
|
logrus.Debugf("|<--- Resize Message (receiver:%v): %+v", s.prefix, m.Resize)
|
||||||
|
case *pb.Message_Signal:
|
||||||
|
logrus.Debugf("|<--- Signal Message (receiver:%v): %s", s.prefix, m.Signal.Name)
|
||||||
|
}
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
439
controller/remote/server.go
Normal file
439
controller/remote/server.go
Normal file
@@ -0,0 +1,439 @@
|
|||||||
|
package remote
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/build"
|
||||||
|
controllererrors "github.com/docker/buildx/controller/errdefs"
|
||||||
|
"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/buildx/version"
|
||||||
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BuildFunc func(ctx context.Context, options *pb.BuildOptions, stdin io.Reader, progress progress.Writer) (resp *client.SolveResponse, res *build.ResultHandle, err error)
|
||||||
|
|
||||||
|
func NewServer(buildFunc BuildFunc) *Server {
|
||||||
|
return &Server{
|
||||||
|
buildFunc: buildFunc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
buildFunc BuildFunc
|
||||||
|
session map[string]*session
|
||||||
|
sessionMu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
type session struct {
|
||||||
|
buildOnGoing atomic.Bool
|
||||||
|
statusChan chan *pb.StatusResponse
|
||||||
|
cancelBuild func()
|
||||||
|
buildOptions *pb.BuildOptions
|
||||||
|
inputPipe *io.PipeWriter
|
||||||
|
|
||||||
|
result *build.ResultHandle
|
||||||
|
|
||||||
|
processes *processes.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *session) cancelRunningProcesses() {
|
||||||
|
s.processes.CancelRunningProcesses()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) ListProcesses(ctx context.Context, req *pb.ListProcessesRequest) (res *pb.ListProcessesResponse, err error) {
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
defer m.sessionMu.Unlock()
|
||||||
|
s, ok := m.session[req.Ref]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("unknown ref %q", req.Ref)
|
||||||
|
}
|
||||||
|
res = new(pb.ListProcessesResponse)
|
||||||
|
res.Infos = append(res.Infos, s.processes.ListProcesses()...)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) DisconnectProcess(ctx context.Context, req *pb.DisconnectProcessRequest) (res *pb.DisconnectProcessResponse, err error) {
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
defer m.sessionMu.Unlock()
|
||||||
|
s, ok := m.session[req.Ref]
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.Errorf("unknown ref %q", req.Ref)
|
||||||
|
}
|
||||||
|
return res, s.processes.DeleteProcess(req.ProcessID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) Info(ctx context.Context, req *pb.InfoRequest) (res *pb.InfoResponse, err error) {
|
||||||
|
return &pb.InfoResponse{
|
||||||
|
BuildxVersion: &pb.BuildxVersion{
|
||||||
|
Package: version.Package,
|
||||||
|
Version: version.Version,
|
||||||
|
Revision: version.Revision,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) List(ctx context.Context, req *pb.ListRequest) (res *pb.ListResponse, err error) {
|
||||||
|
keys := make(map[string]struct{})
|
||||||
|
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
for k := range m.session {
|
||||||
|
keys[k] = struct{}{}
|
||||||
|
}
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
|
||||||
|
var keysL []string
|
||||||
|
for k := range keys {
|
||||||
|
keysL = append(keysL, k)
|
||||||
|
}
|
||||||
|
return &pb.ListResponse{
|
||||||
|
Keys: keysL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) Disconnect(ctx context.Context, req *pb.DisconnectRequest) (res *pb.DisconnectResponse, err error) {
|
||||||
|
key := req.Ref
|
||||||
|
if key == "" {
|
||||||
|
return nil, errors.New("disconnect: empty key")
|
||||||
|
}
|
||||||
|
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
if s, ok := m.session[key]; ok {
|
||||||
|
if s.cancelBuild != nil {
|
||||||
|
s.cancelBuild()
|
||||||
|
}
|
||||||
|
s.cancelRunningProcesses()
|
||||||
|
if s.result != nil {
|
||||||
|
s.result.Done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete(m.session, key)
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
|
||||||
|
return &pb.DisconnectResponse{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) Close() error {
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
for k := range m.session {
|
||||||
|
if s, ok := m.session[k]; ok {
|
||||||
|
if s.cancelBuild != nil {
|
||||||
|
s.cancelBuild()
|
||||||
|
}
|
||||||
|
s.cancelRunningProcesses()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) Inspect(ctx context.Context, req *pb.InspectRequest) (*pb.InspectResponse, error) {
|
||||||
|
ref := req.Ref
|
||||||
|
if ref == "" {
|
||||||
|
return nil, errors.New("inspect: empty key")
|
||||||
|
}
|
||||||
|
var bo *pb.BuildOptions
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
if s, ok := m.session[ref]; ok {
|
||||||
|
bo = s.buildOptions
|
||||||
|
} else {
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
return nil, errors.Errorf("inspect: unknown key %v", ref)
|
||||||
|
}
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
return &pb.InspectResponse{Options: bo}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) Build(ctx context.Context, req *pb.BuildRequest) (*pb.BuildResponse, error) {
|
||||||
|
ref := req.Ref
|
||||||
|
if ref == "" {
|
||||||
|
return nil, errors.New("build: empty key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare status channel and session
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
if m.session == nil {
|
||||||
|
m.session = make(map[string]*session)
|
||||||
|
}
|
||||||
|
s, ok := m.session[ref]
|
||||||
|
if ok {
|
||||||
|
if !s.buildOnGoing.CompareAndSwap(false, true) {
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
return &pb.BuildResponse{}, errors.New("build ongoing")
|
||||||
|
}
|
||||||
|
s.cancelRunningProcesses()
|
||||||
|
s.result = nil
|
||||||
|
} else {
|
||||||
|
s = &session{}
|
||||||
|
s.buildOnGoing.Store(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.processes = processes.NewManager()
|
||||||
|
statusChan := make(chan *pb.StatusResponse)
|
||||||
|
s.statusChan = statusChan
|
||||||
|
inR, inW := io.Pipe()
|
||||||
|
defer inR.Close()
|
||||||
|
s.inputPipe = inW
|
||||||
|
m.session[ref] = s
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
defer func() {
|
||||||
|
close(statusChan)
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
s, ok := m.session[ref]
|
||||||
|
if ok {
|
||||||
|
s.statusChan = nil
|
||||||
|
s.buildOnGoing.Store(false)
|
||||||
|
}
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
pw := pb.NewProgressWriter(statusChan)
|
||||||
|
|
||||||
|
// Build the specified request
|
||||||
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
defer cancel()
|
||||||
|
resp, res, buildErr := m.buildFunc(ctx, req.Options, inR, pw)
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
if s, ok := m.session[ref]; ok {
|
||||||
|
// NOTE: buildFunc can return *build.ResultHandle even on error (e.g. when it's implemented using (github.com/docker/buildx/controller/build).RunBuild).
|
||||||
|
if res != nil {
|
||||||
|
s.result = res
|
||||||
|
s.cancelBuild = cancel
|
||||||
|
s.buildOptions = req.Options
|
||||||
|
m.session[ref] = s
|
||||||
|
if buildErr != nil {
|
||||||
|
buildErr = controllererrors.WrapBuild(buildErr, ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
return nil, errors.Errorf("build: unknown key %v", ref)
|
||||||
|
}
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
|
||||||
|
if buildErr != nil {
|
||||||
|
return nil, buildErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp == nil {
|
||||||
|
resp = &client.SolveResponse{}
|
||||||
|
}
|
||||||
|
return &pb.BuildResponse{
|
||||||
|
ExporterResponse: resp.ExporterResponse,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) Status(req *pb.StatusRequest, stream pb.Controller_StatusServer) error {
|
||||||
|
ref := req.Ref
|
||||||
|
if ref == "" {
|
||||||
|
return errors.New("status: empty key")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait and get status channel prepared by Build()
|
||||||
|
var statusChan <-chan *pb.StatusResponse
|
||||||
|
for {
|
||||||
|
// TODO: timeout?
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
if _, ok := m.session[ref]; !ok || m.session[ref].statusChan == nil {
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
time.Sleep(time.Millisecond) // TODO: wait Build without busy loop and make it cancellable
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
statusChan = m.session[ref].statusChan
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// forward status
|
||||||
|
for ss := range statusChan {
|
||||||
|
if ss == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err := stream.Send(ss); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) Input(stream pb.Controller_InputServer) (err error) {
|
||||||
|
// Get the target ref from init message
|
||||||
|
msg, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
init := msg.GetInit()
|
||||||
|
if init == nil {
|
||||||
|
return errors.Errorf("unexpected message: %T; wanted init", msg.GetInit())
|
||||||
|
}
|
||||||
|
ref := init.Ref
|
||||||
|
if ref == "" {
|
||||||
|
return errors.New("input: no ref is provided")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait and get input stream pipe prepared by Build()
|
||||||
|
var inputPipeW *io.PipeWriter
|
||||||
|
for {
|
||||||
|
// TODO: timeout?
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
if _, ok := m.session[ref]; !ok || m.session[ref].inputPipe == nil {
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
time.Sleep(time.Millisecond) // TODO: wait Build without busy loop and make it cancellable
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inputPipeW = m.session[ref].inputPipe
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forward input stream
|
||||||
|
eg, ctx := errgroup.WithContext(context.TODO())
|
||||||
|
done := make(chan struct{})
|
||||||
|
msgCh := make(chan *pb.InputMessage)
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer close(msgCh)
|
||||||
|
for {
|
||||||
|
msg, err := stream.Recv()
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, io.EOF) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case msgCh <- msg:
|
||||||
|
case <-done:
|
||||||
|
return nil
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
eg.Go(func() (retErr error) {
|
||||||
|
defer close(done)
|
||||||
|
defer func() {
|
||||||
|
if retErr != nil {
|
||||||
|
inputPipeW.CloseWithError(retErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
inputPipeW.Close()
|
||||||
|
}()
|
||||||
|
for {
|
||||||
|
var msg *pb.InputMessage
|
||||||
|
select {
|
||||||
|
case msg = <-msgCh:
|
||||||
|
case <-ctx.Done():
|
||||||
|
return errors.Wrap(ctx.Err(), "canceled")
|
||||||
|
}
|
||||||
|
if msg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if data := msg.GetData(); data != nil {
|
||||||
|
if len(data.Data) > 0 {
|
||||||
|
_, err := inputPipeW.Write(data.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if data.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return eg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Server) Invoke(srv pb.Controller_InvokeServer) error {
|
||||||
|
containerIn, containerOut := ioset.Pipe()
|
||||||
|
defer func() { containerOut.Close(); containerIn.Close() }()
|
||||||
|
|
||||||
|
initDoneCh := make(chan *processes.Process)
|
||||||
|
initErrCh := make(chan error)
|
||||||
|
eg, egCtx := errgroup.WithContext(context.TODO())
|
||||||
|
srvIOCtx, srvIOCancel := context.WithCancel(egCtx)
|
||||||
|
eg.Go(func() error {
|
||||||
|
defer srvIOCancel()
|
||||||
|
return serveIO(srvIOCtx, srv, func(initMessage *pb.InitMessage) (retErr error) {
|
||||||
|
defer func() {
|
||||||
|
if retErr != nil {
|
||||||
|
initErrCh <- retErr
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
ref := initMessage.Ref
|
||||||
|
cfg := initMessage.InvokeConfig
|
||||||
|
|
||||||
|
m.sessionMu.Lock()
|
||||||
|
s, ok := m.session[ref]
|
||||||
|
if !ok {
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
return errors.Errorf("invoke: unknown key %v", ref)
|
||||||
|
}
|
||||||
|
m.sessionMu.Unlock()
|
||||||
|
|
||||||
|
pid := initMessage.ProcessID
|
||||||
|
if pid == "" {
|
||||||
|
return errors.Errorf("invoke: specify process ID")
|
||||||
|
}
|
||||||
|
proc, ok := s.processes.Get(pid)
|
||||||
|
if !ok {
|
||||||
|
// Start a new process.
|
||||||
|
if cfg == nil {
|
||||||
|
return errors.New("no container config is provided")
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
proc, err = s.processes.StartProcess(pid, s.result, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Attach containerIn to this process
|
||||||
|
proc.ForwardIO(&containerIn, srvIOCancel)
|
||||||
|
initDoneCh <- proc
|
||||||
|
return nil
|
||||||
|
}, &ioServerConfig{
|
||||||
|
stdin: containerOut.Stdin,
|
||||||
|
stdout: containerOut.Stdout,
|
||||||
|
stderr: containerOut.Stderr,
|
||||||
|
// TODO: signal, resize
|
||||||
|
})
|
||||||
|
})
|
||||||
|
eg.Go(func() (rErr error) {
|
||||||
|
defer srvIOCancel()
|
||||||
|
// Wait for init done
|
||||||
|
var proc *processes.Process
|
||||||
|
select {
|
||||||
|
case p := <-initDoneCh:
|
||||||
|
proc = p
|
||||||
|
case err := <-initErrCh:
|
||||||
|
return err
|
||||||
|
case <-egCtx.Done():
|
||||||
|
return egCtx.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for IO done
|
||||||
|
select {
|
||||||
|
case <-srvIOCtx.Done():
|
||||||
|
return srvIOCtx.Err()
|
||||||
|
case err := <-proc.Done():
|
||||||
|
return err
|
||||||
|
case <-egCtx.Done():
|
||||||
|
return egCtx.Err()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return eg.Wait()
|
||||||
|
}
|
||||||
184
docker-bake.hcl
Normal file
184
docker-bake.hcl
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
variable "GO_VERSION" {
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
variable "DOCS_FORMATS" {
|
||||||
|
default = "md"
|
||||||
|
}
|
||||||
|
variable "DESTDIR" {
|
||||||
|
default = "./bin"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Special target: https://github.com/docker/metadata-action#bake-definition
|
||||||
|
target "meta-helper" {
|
||||||
|
tags = ["docker/buildx-bin:local"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "_common" {
|
||||||
|
args = {
|
||||||
|
GO_VERSION = GO_VERSION
|
||||||
|
BUILDKIT_CONTEXT_KEEP_GIT_DIR = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = ["binaries"]
|
||||||
|
}
|
||||||
|
|
||||||
|
group "validate" {
|
||||||
|
targets = ["lint", "validate-vendor", "validate-docs"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "lint" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
dockerfile = "./hack/dockerfiles/lint.Dockerfile"
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "validate-vendor" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
|
||||||
|
target = "validate"
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "validate-docs" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
args = {
|
||||||
|
FORMATS = DOCS_FORMATS
|
||||||
|
BUILDX_EXPERIMENTAL = 1 // enables experimental cmds/flags for docs generation
|
||||||
|
}
|
||||||
|
dockerfile = "./hack/dockerfiles/docs.Dockerfile"
|
||||||
|
target = "validate"
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "validate-authors" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
dockerfile = "./hack/dockerfiles/authors.Dockerfile"
|
||||||
|
target = "validate"
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "validate-generated-files" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
dockerfile = "./hack/dockerfiles/generated-files.Dockerfile"
|
||||||
|
target = "validate"
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "update-vendor" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
|
||||||
|
target = "update"
|
||||||
|
output = ["."]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "update-docs" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
args = {
|
||||||
|
FORMATS = DOCS_FORMATS
|
||||||
|
BUILDX_EXPERIMENTAL = 1 // enables experimental cmds/flags for docs generation
|
||||||
|
}
|
||||||
|
dockerfile = "./hack/dockerfiles/docs.Dockerfile"
|
||||||
|
target = "update"
|
||||||
|
output = ["./docs/reference"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "update-authors" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
dockerfile = "./hack/dockerfiles/authors.Dockerfile"
|
||||||
|
target = "update"
|
||||||
|
output = ["."]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "update-generated-files" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
dockerfile = "./hack/dockerfiles/generated-files.Dockerfile"
|
||||||
|
target = "update"
|
||||||
|
output = ["."]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "mod-outdated" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
|
||||||
|
target = "outdated"
|
||||||
|
no-cache-filter = ["outdated"]
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "test" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
target = "test-coverage"
|
||||||
|
output = ["${DESTDIR}/coverage"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "binaries" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
target = "binaries"
|
||||||
|
output = ["${DESTDIR}/build"]
|
||||||
|
platforms = ["local"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "binaries-cross" {
|
||||||
|
inherits = ["binaries"]
|
||||||
|
platforms = [
|
||||||
|
"darwin/amd64",
|
||||||
|
"darwin/arm64",
|
||||||
|
"linux/amd64",
|
||||||
|
"linux/arm/v6",
|
||||||
|
"linux/arm/v7",
|
||||||
|
"linux/arm64",
|
||||||
|
"linux/ppc64le",
|
||||||
|
"linux/riscv64",
|
||||||
|
"linux/s390x",
|
||||||
|
"windows/amd64",
|
||||||
|
"windows/arm64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "release" {
|
||||||
|
inherits = ["binaries-cross"]
|
||||||
|
target = "release"
|
||||||
|
output = ["${DESTDIR}/release"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "image" {
|
||||||
|
inherits = ["meta-helper", "binaries"]
|
||||||
|
output = ["type=image"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "image-cross" {
|
||||||
|
inherits = ["meta-helper", "binaries-cross"]
|
||||||
|
output = ["type=image"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "image-local" {
|
||||||
|
inherits = ["image"]
|
||||||
|
output = ["type=docker"]
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "HTTP_PROXY" {
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
variable "HTTPS_PROXY" {
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
variable "NO_PROXY" {
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
target "integration-test-base" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
args = {
|
||||||
|
HTTP_PROXY = HTTP_PROXY
|
||||||
|
HTTPS_PROXY = HTTPS_PROXY
|
||||||
|
NO_PROXY = NO_PROXY
|
||||||
|
}
|
||||||
|
target = "integration-test-base"
|
||||||
|
output = ["type=cacheonly"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "integration-test" {
|
||||||
|
inherits = ["integration-test-base"]
|
||||||
|
target = "integration-test"
|
||||||
|
}
|
||||||
1088
docs/bake-reference.md
Normal file
1088
docs/bake-reference.md
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user