mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-09-14 14:59:08 +08:00
Compare commits
650 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed00243a0c | ||
|
|
1223e759a4 | ||
|
|
4fd3ec1a50 | ||
|
|
7f9cad1e4e | ||
|
|
437b8b140f | ||
|
|
8f0d9bd71f | ||
|
|
9c22be5d9c | ||
|
|
42dea89247 | ||
|
|
982a332679 | ||
|
|
441853f189 | ||
|
|
611329fc7f | ||
|
|
f3c135e583 | ||
|
|
7f84582b37 | ||
|
|
297526c49d | ||
|
|
d01d394a2b | ||
|
|
17d4369866 | ||
|
|
fb5e1393a4 | ||
|
|
18dbde9ed6 | ||
|
|
da1f4b8496 | ||
|
|
5b2e1d3ce4 | ||
|
|
7d8a6bc1d7 | ||
|
|
a378f8095e | ||
|
|
005bc009e8 | ||
|
|
3bc7d4bec6 | ||
|
|
96c1b05238 | ||
|
|
98f9f806f3 | ||
|
|
c834ba1389 | ||
|
|
cab437adef | ||
|
|
eefa8188e1 | ||
|
|
1d8db8a738 | ||
|
|
75ddc5b811 | ||
|
|
17dc0e1108 | ||
|
|
64ac6c9621 | ||
|
|
a7753ea781 | ||
|
|
12a6eb5b22 | ||
|
|
74b21258b6 | ||
|
|
2f9d46ce27 | ||
|
|
7b660c4e30 | ||
|
|
406799eb1c | ||
|
|
ef0cbf20f4 | ||
|
|
7f572eb044 | ||
|
|
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 | ||
|
|
f349ba8750 |
@@ -1,2 +1,3 @@
|
|||||||
bin/
|
bin/
|
||||||
cross-out/
|
cross-out/
|
||||||
|
release-out/
|
||||||
|
|||||||
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"
|
||||||
281
.github/workflows/build.yml
vendored
281
.github/workflows/build.yml
vendored
@@ -1,185 +1,214 @@
|
|||||||
name: build
|
name: build
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
|
- 'v[0-9]*'
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
|
- 'v[0-9]*'
|
||||||
|
|
||||||
env:
|
env:
|
||||||
REPO_SLUG_ORIGIN: "moby/buildkit:master"
|
REPO_SLUG: "docker/buildx-bin"
|
||||||
CACHEKEY_BINARIES: "binaries"
|
RELEASE_OUT: "./release-out"
|
||||||
PLATFORMS: "linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/s390x,linux/ppc64le"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
base:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Checkout
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
-
|
|
||||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
|
||||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
|
||||||
-
|
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
with:
|
|
||||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
|
||||||
-
|
|
||||||
name: Build ${{ env.CACHEKEY_BINARIES }}
|
|
||||||
run: |
|
|
||||||
./hack/build_ci_first_pass binaries
|
|
||||||
env:
|
|
||||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
|
||||||
CACHEDIR_TO: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}-new
|
|
||||||
-
|
|
||||||
# FIXME: Temp fix for https://github.com/moby/buildkit/issues/1850
|
|
||||||
name: Move cache
|
|
||||||
run: |
|
|
||||||
rm -rf /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
|
||||||
mv /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}-new /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [base]
|
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
-
|
|
||||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
|
||||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
|
||||||
-
|
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
with:
|
with:
|
||||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
version: latest
|
||||||
-
|
-
|
||||||
name: Test
|
name: Test
|
||||||
run: |
|
uses: docker/bake-action@v2
|
||||||
make test
|
with:
|
||||||
env:
|
targets: test
|
||||||
TEST_COVERAGE: 1
|
set: |
|
||||||
TESTFLAGS: -v --parallel=6 --timeout=20m
|
*.cache-from=type=gha,scope=test
|
||||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
*.cache-to=type=gha,scope=test
|
||||||
-
|
-
|
||||||
name: Send to Codecov
|
name: Upload coverage
|
||||||
uses: codecov/codecov-action@v1
|
uses: codecov/codecov-action@v3
|
||||||
with:
|
with:
|
||||||
file: ./coverage/coverage.txt
|
file: ./coverage/coverage.txt
|
||||||
|
|
||||||
cross:
|
prepare:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [base]
|
outputs:
|
||||||
|
matrix: ${{ steps.platforms.outputs.matrix }}
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
-
|
-
|
||||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
name: Create matrix
|
||||||
uses: actions/cache@v2
|
id: platforms
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
|
||||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
|
||||||
-
|
|
||||||
name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v1
|
|
||||||
-
|
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v1
|
|
||||||
with:
|
|
||||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
|
||||||
-
|
|
||||||
name: Cross
|
|
||||||
run: |
|
run: |
|
||||||
make cross
|
echo ::set-output name=matrix::$(docker buildx bake binaries-cross --print | jq -cr '.target."binaries-cross".platforms')
|
||||||
env:
|
-
|
||||||
TARGETPLATFORM: ${{ env.PLATFORMS }},darwin/amd64,windows/amd64
|
name: Show matrix
|
||||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
run: |
|
||||||
|
echo ${{ steps.platforms.outputs.matrix }}
|
||||||
|
|
||||||
binaries:
|
binaries:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [test, cross]
|
needs:
|
||||||
|
- prepare
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
platform: ${{ fromJson(needs.prepare.outputs.matrix) }}
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Prepare
|
||||||
|
run: |
|
||||||
|
platform=${{ matrix.platform }}
|
||||||
|
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
-
|
||||||
|
name: Build
|
||||||
|
uses: docker/bake-action@v2
|
||||||
|
with:
|
||||||
|
targets: release
|
||||||
|
set: |
|
||||||
|
*.platform=${{ 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.RELEASE_OUT }}/*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
bin-image:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
-
|
|
||||||
name: Prepare
|
|
||||||
id: prep
|
|
||||||
run: |
|
|
||||||
TAG=pr
|
|
||||||
if [[ $GITHUB_REF == refs/tags/v* ]]; then
|
|
||||||
TAG=${GITHUB_REF#refs/tags/}
|
|
||||||
elif [[ $GITHUB_REF == refs/heads/* ]]; then
|
|
||||||
TAG=$(echo ${GITHUB_REF#refs/heads/} | sed -r 's#/+#-#g')
|
|
||||||
fi
|
|
||||||
echo ::set-output name=tag::${TAG}
|
|
||||||
-
|
|
||||||
name: Cache ${{ env.CACHEKEY_BINARIES }}
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
|
||||||
key: ${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-${{ github.sha }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-${{ env.CACHEKEY_BINARIES }}-
|
|
||||||
-
|
-
|
||||||
name: Set up QEMU
|
name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v1
|
uses: docker/setup-qemu-action@v2
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
with:
|
with:
|
||||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
version: latest
|
||||||
-
|
-
|
||||||
name: Build ${{ steps.prep.outputs.tag }}
|
name: Docker meta
|
||||||
run: |
|
id: meta
|
||||||
./hack/release "${{ steps.prep.outputs.tag }}" release-out
|
uses: docker/metadata-action@v4
|
||||||
env:
|
with:
|
||||||
PLATFORMS: ${{ env.PLATFORMS }},darwin/amd64,windows/amd64
|
images: |
|
||||||
CACHEDIR_FROM: /tmp/.buildx-cache/${{ env.CACHEKEY_BINARIES }}
|
${{ env.REPO_SLUG }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
bake-target: meta-helper
|
||||||
-
|
-
|
||||||
name: Move artifacts
|
name: Login to DockerHub
|
||||||
run: |
|
if: github.event_name != 'pull_request'
|
||||||
mv ./release-out/**/* ./release-out/
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
-
|
-
|
||||||
name: Upload artifacts
|
name: Build and push image
|
||||||
uses: actions/upload-artifact@v2
|
uses: docker/bake-action@v2
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
./docker-bake.hcl
|
||||||
|
${{ steps.meta.outputs.bake-file }}
|
||||||
|
targets: image-cross
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
set: |
|
||||||
|
*.cache-from=type=gha,scope=bin-image
|
||||||
|
*.cache-to=type=gha,scope=bin-image,mode=max
|
||||||
|
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- binaries
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Download binaries
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: buildx
|
name: buildx
|
||||||
path: ./release-out/*
|
path: ${{ env.RELEASE_OUT }}
|
||||||
if-no-files-found: error
|
-
|
||||||
|
name: Create checksums
|
||||||
|
run: ./hack/hash-files
|
||||||
|
-
|
||||||
|
name: List artifacts
|
||||||
|
run: |
|
||||||
|
tree -nh ${{ env.RELEASE_OUT }}
|
||||||
|
-
|
||||||
|
name: Check artifacts
|
||||||
|
run: |
|
||||||
|
find ${{ env.RELEASE_OUT }} -type f -exec file -e ascii -- {} +
|
||||||
-
|
-
|
||||||
name: GitHub Release
|
name: GitHub Release
|
||||||
if: startsWith(github.ref, 'refs/tags/v')
|
if: startsWith(github.ref, 'refs/tags/v')
|
||||||
uses: softprops/action-gh-release@v1
|
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
draft: true
|
draft: true
|
||||||
files: ./release-out/*
|
files: ${{ env.RELEASE_OUT }}/*
|
||||||
name: ${{ steps.prep.outputs.tag }}
|
|
||||||
|
buildkit-edge:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
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@v2
|
||||||
|
with:
|
||||||
|
targets: binaries
|
||||||
|
|||||||
56
.github/workflows/docs-release.yml
vendored
Normal file
56
.github/workflows/docs-release.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
name: docs-release
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [ released ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
open-pr:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout docs repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GHPAT_DOCS_DISPATCH }}
|
||||||
|
repository: docker/docker.github.io
|
||||||
|
ref: master
|
||||||
|
-
|
||||||
|
name: Prepare
|
||||||
|
run: |
|
||||||
|
rm -rf ./_data/buildx/*
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
-
|
||||||
|
name: Build docs
|
||||||
|
uses: docker/bake-action@v2
|
||||||
|
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@923ad837f191474af6b1721408744feb989a4c27 # v4.0.4
|
||||||
|
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
|
||||||
118
.github/workflows/docs-upstream.yml
vendored
Normal file
118
.github/workflows/docs-upstream.yml
vendored
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
# 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-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
-
|
||||||
|
name: Build reference YAML docs
|
||||||
|
uses: docker/bake-action@v2
|
||||||
|
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:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- docs-yaml
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
repository: docker/docker.github.io
|
||||||
|
-
|
||||||
|
name: Install js-yaml
|
||||||
|
run: npm install js-yaml
|
||||||
|
-
|
||||||
|
# use the actual buildx ref that triggers this workflow, so we make
|
||||||
|
# sure pages fetched by docs repo are still valid
|
||||||
|
# https://github.com/docker/docker.github.io/blob/98c7c9535063ae4cd2cd0a31478a21d16d2f07a3/_config.yml#L164-L173
|
||||||
|
name: Set correct ref to fetch remote resources
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const fs = require('fs');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
|
||||||
|
const configFile = '_config.yml'
|
||||||
|
const config = yaml.load(fs.readFileSync(configFile, 'utf8'));
|
||||||
|
for (const remote of config['fetch-remote']) {
|
||||||
|
if (remote['repo'] != 'https://github.com/docker/buildx') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
remote['ref'] = "${{ github.ref }}";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(configFile, yaml.dump(config), 'utf8')
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err.message)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
-
|
||||||
|
name: Prepare
|
||||||
|
run: |
|
||||||
|
# print docs jekyll config updated in previous step
|
||||||
|
yq _config.yml
|
||||||
|
# cleanup reference yaml docs and js-yaml module
|
||||||
|
rm -rf ./_data/buildx/* ./node_modules
|
||||||
|
-
|
||||||
|
name: Download built reference YAML docs
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: docs-yaml
|
||||||
|
path: ./_data/buildx/
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
-
|
||||||
|
name: Validate
|
||||||
|
uses: docker/bake-action@v2
|
||||||
|
with:
|
||||||
|
targets: validate
|
||||||
|
set: |
|
||||||
|
*.cache-from=type=gha,scope=docs-upstream
|
||||||
|
*.cache-to=type=gha,scope=docs-upstream,mode=max
|
||||||
166
.github/workflows/e2e.yml
vendored
Normal file
166
.github/workflows/e2e.yml
vendored
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
name: e2e
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v[0-9]*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- 'master'
|
||||||
|
- 'v[0-9]*'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
env:
|
||||||
|
BIN_OUT: ./bin
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
-
|
||||||
|
name: Build
|
||||||
|
uses: docker/bake-action@v2
|
||||||
|
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.BIN_OUT }}/buildx ${{ env.BIN_OUT }}/docker-buildx
|
||||||
|
-
|
||||||
|
name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: binary
|
||||||
|
path: ${{ env.BIN_OUT }}
|
||||||
|
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@v3
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
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: debianmaster/actions-k3s@b9cf3f599fd118699a3c8a0d18a2f2bda6cf4ce4
|
||||||
|
id: k3s
|
||||||
|
with:
|
||||||
|
version: v1.21.2-k3s1
|
||||||
|
-
|
||||||
|
name: Config k3s
|
||||||
|
if: matrix.driver == 'kubernetes'
|
||||||
|
run: |
|
||||||
|
(set -x ; cat ${{ steps.k3s.outputs.kubeconfig }})
|
||||||
|
-
|
||||||
|
name: Check k3s nodes
|
||||||
|
if: matrix.driver == 'kubernetes'
|
||||||
|
run: |
|
||||||
|
kubectl get nodes
|
||||||
|
-
|
||||||
|
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 }}
|
||||||
25
.github/workflows/godev.yml
vendored
25
.github/workflows/godev.yml
vendored
@@ -1,25 +0,0 @@
|
|||||||
# Workflow used to make a request to proxy.golang.org to refresh cache on https://pkg.go.dev/github.com/docker/buildx
|
|
||||||
# when a released of buildx is produced
|
|
||||||
name: godev
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- 'v*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
update:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
-
|
|
||||||
name: Set up Go
|
|
||||||
uses: actions/setup-go@v2
|
|
||||||
with:
|
|
||||||
go-version: 1.13
|
|
||||||
-
|
|
||||||
name: Call pkg.go.dev
|
|
||||||
run: |
|
|
||||||
go get github.com/${GITHUB_REPOSITORY}@${GITHUB_REF#refs/tags/}
|
|
||||||
env:
|
|
||||||
GO111MODULE: on
|
|
||||||
GOPROXY: https://proxy.golang.org
|
|
||||||
16
.github/workflows/validate.yml
vendored
16
.github/workflows/validate.yml
vendored
@@ -1,18 +1,21 @@
|
|||||||
name: validate
|
name: validate
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
|
- 'v[0-9]*'
|
||||||
tags:
|
tags:
|
||||||
- 'v*'
|
- 'v*'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- 'master'
|
- 'master'
|
||||||
|
- 'v[0-9]*'
|
||||||
env:
|
|
||||||
REPO_SLUG_ORIGIN: "moby/buildkit:master"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
validate:
|
validate:
|
||||||
@@ -23,15 +26,16 @@ jobs:
|
|||||||
target:
|
target:
|
||||||
- lint
|
- lint
|
||||||
- validate-vendor
|
- validate-vendor
|
||||||
|
- validate-docs
|
||||||
steps:
|
steps:
|
||||||
-
|
-
|
||||||
name: Checkout
|
name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v3
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v1
|
uses: docker/setup-buildx-action@v2
|
||||||
with:
|
with:
|
||||||
driver-opts: image=${{ env.REPO_SLUG_ORIGIN }}
|
version: latest
|
||||||
-
|
-
|
||||||
name: Run
|
name: Run
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
bin
|
bin
|
||||||
coverage
|
coverage
|
||||||
cross-out
|
cross-out
|
||||||
|
release-out
|
||||||
|
|||||||
40
.golangci.yml
Normal file
40
.golangci.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
run:
|
||||||
|
timeout: 10m
|
||||||
|
skip-files:
|
||||||
|
- ".*\\.pb\\.go$"
|
||||||
|
|
||||||
|
modules-download-mode: vendor
|
||||||
|
|
||||||
|
build-tags:
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable:
|
||||||
|
- gofmt
|
||||||
|
- govet
|
||||||
|
- deadcode
|
||||||
|
- depguard
|
||||||
|
- goimports
|
||||||
|
- ineffassign
|
||||||
|
- misspell
|
||||||
|
- unused
|
||||||
|
- varcheck
|
||||||
|
- revive
|
||||||
|
- staticcheck
|
||||||
|
- typecheck
|
||||||
|
- structcheck
|
||||||
|
disable-all: true
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
depguard:
|
||||||
|
list-type: blacklist
|
||||||
|
include-go-root: true
|
||||||
|
packages:
|
||||||
|
# The io/ioutil package has been deprecated.
|
||||||
|
# https://go.dev/doc/go1.16#ioutil
|
||||||
|
- io/ioutil
|
||||||
|
|
||||||
|
issues:
|
||||||
|
exclude-rules:
|
||||||
|
- linters:
|
||||||
|
- revive
|
||||||
|
text: "stutters"
|
||||||
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>
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
ignore: |
|
|
||||||
/vendor
|
|
||||||
|
|
||||||
extends: default
|
|
||||||
|
|
||||||
yaml-files:
|
|
||||||
- '*.yaml'
|
|
||||||
- '*.yml'
|
|
||||||
|
|
||||||
rules:
|
|
||||||
truthy: disable
|
|
||||||
line-length: disable
|
|
||||||
document-start: disable
|
|
||||||
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>
|
||||||
|
|||||||
63
Dockerfile
63
Dockerfile
@@ -1,17 +1,21 @@
|
|||||||
# syntax=docker/dockerfile:1.1-experimental
|
# syntax=docker/dockerfile:1.4
|
||||||
|
|
||||||
ARG DOCKERD_VERSION=19.03-rc
|
ARG GO_VERSION=1.18
|
||||||
ARG CLI_VERSION=19.03
|
ARG XX_VERSION=1.1.2
|
||||||
|
ARG DOCKERD_VERSION=20.10.14
|
||||||
|
|
||||||
FROM docker:$DOCKERD_VERSION AS dockerd-release
|
FROM docker:$DOCKERD_VERSION AS dockerd-release
|
||||||
|
|
||||||
# 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 gobase AS buildx-version
|
FROM gobase AS buildx-version
|
||||||
@@ -21,56 +25,55 @@ RUN --mount=target=. \
|
|||||||
echo -n "${VERSION}" | tee /tmp/.version;
|
echo -n "${VERSION}" | tee /tmp/.version;
|
||||||
|
|
||||||
FROM gobase AS buildx-build
|
FROM gobase AS buildx-build
|
||||||
ENV CGO_ENABLED=0
|
ARG LDFLAGS="-w -s"
|
||||||
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,source=/tmp/.ldflags,target=/tmp/.ldflags,from=buildx-version \
|
||||||
file /usr/bin/buildx && file /usr/bin/buildx | egrep "statically linked|Mach-O|Windows"
|
set -x; xx-go build -ldflags "$(cat /tmp/.ldflags) ${LDFLAGS}" -o /usr/bin/buildx ./cmd/buildx && \
|
||||||
|
xx-verify --static /usr/bin/buildx
|
||||||
|
|
||||||
FROM buildx-build AS integration-tests
|
FROM gobase AS test
|
||||||
COPY . .
|
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/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/buildx /buildx.exe
|
||||||
|
|
||||||
FROM binaries-$TARGETOS AS binaries
|
FROM binaries-$TARGETOS AS binaries
|
||||||
|
|
||||||
|
# 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,source=/tmp/.version,target=/tmp/.version,from=buildx-version \
|
||||||
mkdir -p /out && cp buildx* "/out/buildx-$(cat /tmp/.version).$(echo $TARGETPLATFORM | sed 's/\//-/g')$(ls buildx* | sed -e 's/^buildx//')"
|
mkdir -p /out && cp buildx* "/out/buildx-$(cat /tmp/.version).$(echo $TARGETPLATFORM | sed 's/\//-/g')$(ls buildx* | sed -e 's/^buildx//')"
|
||||||
|
|
||||||
FROM scratch AS release
|
FROM scratch AS release
|
||||||
COPY --from=releaser /out/ /
|
COPY --from=releaser /out/ /
|
||||||
|
|
||||||
FROM alpine AS demo-env
|
# Shell
|
||||||
|
FROM docker:$DOCKERD_VERSION AS dockerd-release
|
||||||
|
FROM alpine AS shell
|
||||||
RUN apk add --no-cache iptables tmux git vim less openssh
|
RUN apk add --no-cache iptables tmux git vim less openssh
|
||||||
RUN mkdir -p /usr/local/lib/docker/cli-plugins && ln -s /usr/local/bin/buildx /usr/local/lib/docker/cli-plugins/docker-buildx
|
RUN mkdir -p /usr/local/lib/docker/cli-plugins && ln -s /usr/local/bin/buildx /usr/local/lib/docker/cli-plugins/docker-buildx
|
||||||
COPY ./hack/demo-env/entrypoint.sh /usr/local/bin
|
COPY ./hack/demo-env/entrypoint.sh /usr/local/bin
|
||||||
COPY ./hack/demo-env/tmux.conf /root/.tmux.conf
|
COPY ./hack/demo-env/tmux.conf /root/.tmux.conf
|
||||||
COPY --from=dockerd-release /usr/local/bin /usr/local/bin
|
COPY --from=dockerd-release /usr/local/bin /usr/local/bin
|
||||||
#COPY --from=docker-cli-build /go/src/github.com/docker/cli/build/docker /usr/local/bin
|
|
||||||
|
|
||||||
WORKDIR /work
|
WORKDIR /work
|
||||||
COPY ./hack/demo-env/examples .
|
COPY ./hack/demo-env/examples .
|
||||||
COPY --from=binaries / /usr/local/bin/
|
COPY --from=binaries / /usr/local/bin/
|
||||||
|
|||||||
18
MAINTAINERS
18
MAINTAINERS
@@ -150,6 +150,9 @@ made through a pull request.
|
|||||||
[Org.Maintainers]
|
[Org.Maintainers]
|
||||||
|
|
||||||
people = [
|
people = [
|
||||||
|
"akihirosuda",
|
||||||
|
"crazy-max",
|
||||||
|
"jedevc",
|
||||||
"tiborvass",
|
"tiborvass",
|
||||||
"tonistiigi",
|
"tonistiigi",
|
||||||
]
|
]
|
||||||
@@ -176,6 +179,21 @@ made through a pull request.
|
|||||||
# All other sections should refer to people by their canonical key
|
# All other sections should refer to people by their canonical key
|
||||||
# in the people section.
|
# in the people section.
|
||||||
|
|
||||||
|
[people.akihirosuda]
|
||||||
|
Name = "Akihiro Suda"
|
||||||
|
Email = "akihiro.suda.cz@hco.ntt.co.jp"
|
||||||
|
GitHub = "AkihiroSuda"
|
||||||
|
|
||||||
|
[people.crazy-max]
|
||||||
|
Name = "Kevin Alvarez"
|
||||||
|
Email = "contact@crazymax.dev"
|
||||||
|
GitHub = "crazy-max"
|
||||||
|
|
||||||
|
[people.jedevc]
|
||||||
|
Name = "Justin Chadwell"
|
||||||
|
Email = "me@jedevc.com"
|
||||||
|
GitHub = "jedevc"
|
||||||
|
|
||||||
[people.thajeztah]
|
[people.thajeztah]
|
||||||
Name = "Sebastiaan van Stijn"
|
Name = "Sebastiaan van Stijn"
|
||||||
Email = "github@gone.nl"
|
Email = "github@gone.nl"
|
||||||
|
|||||||
54
Makefile
54
Makefile
@@ -1,34 +1,62 @@
|
|||||||
|
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)
|
||||||
|
else
|
||||||
|
$(error "Buildx is required: https://github.com/docker/buildx#installing")
|
||||||
|
endif
|
||||||
|
|
||||||
|
export BIN_OUT = ./bin
|
||||||
|
export RELEASE_OUT = ./release-out
|
||||||
|
|
||||||
shell:
|
shell:
|
||||||
./hack/shell
|
./hack/shell
|
||||||
|
|
||||||
binaries:
|
binaries:
|
||||||
./hack/binaries
|
$(BUILDX_CMD) bake binaries
|
||||||
|
|
||||||
binaries-cross:
|
binaries-cross:
|
||||||
EXPORT_LOCAL=cross-out ./hack/cross
|
$(BUILDX_CMD) bake binaries-cross
|
||||||
|
|
||||||
cross:
|
|
||||||
./hack/cross
|
|
||||||
|
|
||||||
install: binaries
|
install: binaries
|
||||||
mkdir -p ~/.docker/cli-plugins
|
mkdir -p ~/.docker/cli-plugins
|
||||||
cp bin/buildx ~/.docker/cli-plugins/docker-buildx
|
install bin/buildx ~/.docker/cli-plugins/docker-buildx
|
||||||
|
|
||||||
|
release:
|
||||||
|
./hack/release
|
||||||
|
|
||||||
|
validate-all: lint test validate-vendor validate-docs
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
./hack/lint
|
$(BUILDX_CMD) bake lint
|
||||||
|
|
||||||
test:
|
test:
|
||||||
./hack/test
|
$(BUILDX_CMD) bake test
|
||||||
|
|
||||||
validate-vendor:
|
validate-vendor:
|
||||||
./hack/validate-vendor
|
$(BUILDX_CMD) bake validate-vendor
|
||||||
|
|
||||||
validate-all: lint test validate-vendor
|
validate-docs:
|
||||||
|
$(BUILDX_CMD) bake validate-docs
|
||||||
|
|
||||||
|
validate-authors:
|
||||||
|
$(BUILDX_CMD) bake validate-authors
|
||||||
|
|
||||||
|
test-driver:
|
||||||
|
./hack/test-driver
|
||||||
|
|
||||||
vendor:
|
vendor:
|
||||||
./hack/update-vendor
|
./hack/update-vendor
|
||||||
|
|
||||||
generate-authors:
|
docs:
|
||||||
./hack/generate-authors
|
./hack/update-docs
|
||||||
|
|
||||||
.PHONY: vendor lint shell binaries install binaries-cross validate-all generate-authors
|
authors:
|
||||||
|
$(BUILDX_CMD) bake update-authors
|
||||||
|
|
||||||
|
mod-outdated:
|
||||||
|
$(BUILDX_CMD) bake mod-outdated
|
||||||
|
|
||||||
|
.PHONY: shell binaries binaries-cross install release validate-all lint validate-vendor validate-docs validate-authors vendor docs authors
|
||||||
|
|||||||
816
bake/bake.go
816
bake/bake.go
File diff suppressed because it is too large
Load Diff
1069
bake/bake_test.go
1069
bake/bake_test.go
File diff suppressed because it is too large
Load Diff
303
bake/compose.go
303
bake/compose.go
@@ -1,50 +1,44 @@
|
|||||||
package bake
|
package bake
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"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)
|
cfg, err := loader.Load(compose.ConfigDetails{
|
||||||
|
ConfigFiles: cfgs,
|
||||||
|
Environment: envs,
|
||||||
|
}, func(options *loader.Options) {
|
||||||
|
options.SkipNormalization = true
|
||||||
|
})
|
||||||
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 +46,15 @@ func ParseCompose(dt []byte) (*Config, error) {
|
|||||||
g := &Group{Name: "default"}
|
g := &Group{Name: "default"}
|
||||||
|
|
||||||
for _, s := range cfg.Services {
|
for _, s := range cfg.Services {
|
||||||
|
if s.Build == nil {
|
||||||
if reflect.DeepEqual(s.Build, zeroBuildConfig) {
|
|
||||||
// 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 +65,43 @@ 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 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Targets = append(g.Targets, targetName)
|
||||||
t := &Target{
|
t := &Target{
|
||||||
Name: s.Name,
|
Name: targetName,
|
||||||
Context: contextPathP,
|
Context: contextPathP,
|
||||||
Dockerfile: dockerfilePathP,
|
Dockerfile: dockerfilePathP,
|
||||||
|
Tags: s.Build.Tags,
|
||||||
Labels: s.Build.Labels,
|
Labels: s.Build.Labels,
|
||||||
Args: toMap(s.Build.Args),
|
Args: flatten(s.Build.Args.Resolve(func(val string) (string, bool) {
|
||||||
CacheFrom: s.Build.CacheFrom,
|
if val, ok := s.Environment[val]; ok && val != nil {
|
||||||
// TODO: add platforms
|
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 +113,205 @@ 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.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.UnmarshalBytes(dt)
|
||||||
|
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) compose.Mapping {
|
||||||
|
if len(in) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := compose.Mapping{}
|
||||||
|
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/guides/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
|
||||||
@@ -20,15 +22,29 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./dir
|
context: ./dir
|
||||||
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
|
||||||
|
secrets:
|
||||||
|
token:
|
||||||
|
environment: ENV_TOKEN
|
||||||
|
aws:
|
||||||
|
file: /root/.aws/credentials
|
||||||
`)
|
`)
|
||||||
|
|
||||||
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, 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"}, c.Groups[0].Targets)
|
||||||
|
|
||||||
@@ -38,33 +54,38 @@ services:
|
|||||||
})
|
})
|
||||||
require.Equal(t, "db", c.Targets[0].Name)
|
require.Equal(t, "db", c.Targets[0].Name)
|
||||||
require.Equal(t, "./db", *c.Targets[0].Context)
|
require.Equal(t, "./db", *c.Targets[0].Context)
|
||||||
|
require.Equal(t, []string{"docker.io/tonistiigi/db"}, c.Targets[0].Tags)
|
||||||
|
|
||||||
require.Equal(t, "webapp", c.Targets[1].Name)
|
require.Equal(t, "webapp", c.Targets[1].Name)
|
||||||
require.Equal(t, "./dir", *c.Targets[1].Context)
|
require.Equal(t, "./dir", *c.Targets[1].Context)
|
||||||
require.Equal(t, "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, "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)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +97,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 +112,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 +122,527 @@ 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)
|
os.Setenv("FOO", "bar")
|
||||||
require.Error(t, err)
|
defer os.Unsetenv("FOO")
|
||||||
require.Contains(t, err.Error(), "has neither an image nor a build context specified. At least one must be provided")
|
os.Setenv("BAR", "foo")
|
||||||
|
defer os.Unsetenv("BAR")
|
||||||
|
os.Setenv("ZZZ_BAR", "zzz_foo")
|
||||||
|
defer os.Unsetenv("ZZZ_BAR")
|
||||||
|
|
||||||
|
c, err := ParseCompose([]compose.ConfigFile{{Content: dt}}, sliceToMap(os.Environ()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "bar", c.Targets[0].Args["FOO"])
|
||||||
|
require.Equal(t, "zzz_foo", c.Targets[0].Args["BAR"])
|
||||||
|
require.Equal(t, "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": "foo", "CT_TAG": "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": "foo", "FOO": "bsdf -csdf", "NODE_ENV": "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": "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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
207
bake/hcl.go
207
bake/hcl.go
@@ -1,200 +1,42 @@
|
|||||||
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/hashicorp/hcl/v2/hclsyntax"
|
|
||||||
"github.com/hashicorp/hcl/v2/json"
|
|
||||||
"github.com/moby/buildkit/solver/errdefs"
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
"github.com/moby/buildkit/solver/pb"
|
"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,
|
|
||||||
"bytesslice": stdlib.BytesSliceFunc,
|
|
||||||
"chomp": stdlib.ChompFunc,
|
|
||||||
"chunklist": stdlib.ChunklistFunc,
|
|
||||||
"ceil": stdlib.CeilFunc,
|
|
||||||
"csvdecode": stdlib.CSVDecodeFunc,
|
|
||||||
"coalesce": stdlib.CoalesceFunc,
|
|
||||||
"coalescelist": stdlib.CoalesceListFunc,
|
|
||||||
"compact": stdlib.CompactFunc,
|
|
||||||
"concat": stdlib.ConcatFunc,
|
|
||||||
"contains": stdlib.ContainsFunc,
|
|
||||||
"distinct": stdlib.DistinctFunc,
|
|
||||||
"divide": stdlib.DivideFunc,
|
|
||||||
"element": stdlib.ElementFunc,
|
|
||||||
"equal": stdlib.EqualFunc,
|
|
||||||
"flatten": stdlib.FlattenFunc,
|
|
||||||
"floor": stdlib.FloorFunc,
|
|
||||||
"formatdate": stdlib.FormatDateFunc,
|
|
||||||
"format": stdlib.FormatFunc,
|
|
||||||
"formatlist": stdlib.FormatListFunc,
|
|
||||||
"greaterthan": stdlib.GreaterThanFunc,
|
|
||||||
"greaterthanorequalto": stdlib.GreaterThanOrEqualToFunc,
|
|
||||||
"hasindex": stdlib.HasIndexFunc,
|
|
||||||
"indent": stdlib.IndentFunc,
|
|
||||||
"index": stdlib.IndexFunc,
|
|
||||||
"int": stdlib.IntFunc,
|
|
||||||
"jsondecode": stdlib.JSONDecodeFunc,
|
|
||||||
"jsonencode": stdlib.JSONEncodeFunc,
|
|
||||||
"keys": stdlib.KeysFunc,
|
|
||||||
"join": stdlib.JoinFunc,
|
|
||||||
"length": stdlib.LengthFunc,
|
|
||||||
"lessthan": stdlib.LessThanFunc,
|
|
||||||
"lessthanorequalto": stdlib.LessThanOrEqualToFunc,
|
|
||||||
"log": stdlib.LogFunc,
|
|
||||||
"lookup": stdlib.LookupFunc,
|
|
||||||
"lower": stdlib.LowerFunc,
|
|
||||||
"max": stdlib.MaxFunc,
|
|
||||||
"merge": stdlib.MergeFunc,
|
|
||||||
"min": stdlib.MinFunc,
|
|
||||||
"modulo": stdlib.ModuloFunc,
|
|
||||||
"multiply": stdlib.MultiplyFunc,
|
|
||||||
"negate": stdlib.NegateFunc,
|
|
||||||
"notequal": stdlib.NotEqualFunc,
|
|
||||||
"not": stdlib.NotFunc,
|
|
||||||
"or": stdlib.OrFunc,
|
|
||||||
"parseint": stdlib.ParseIntFunc,
|
|
||||||
"pow": stdlib.PowFunc,
|
|
||||||
"range": stdlib.RangeFunc,
|
|
||||||
"regexall": stdlib.RegexAllFunc,
|
|
||||||
"regex": stdlib.RegexFunc,
|
|
||||||
"reverse": stdlib.ReverseFunc,
|
|
||||||
"reverselist": stdlib.ReverseListFunc,
|
|
||||||
"sethaselement": stdlib.SetHasElementFunc,
|
|
||||||
"setintersection": stdlib.SetIntersectionFunc,
|
|
||||||
"setsubtract": stdlib.SetSubtractFunc,
|
|
||||||
"setsymmetricdifference": stdlib.SetSymmetricDifferenceFunc,
|
|
||||||
"setunion": stdlib.SetUnionFunc,
|
|
||||||
"signum": stdlib.SignumFunc,
|
|
||||||
"slice": stdlib.SliceFunc,
|
|
||||||
"sort": stdlib.SortFunc,
|
|
||||||
"split": stdlib.SplitFunc,
|
|
||||||
"strlen": stdlib.StrlenFunc,
|
|
||||||
"substr": stdlib.SubstrFunc,
|
|
||||||
"subtract": stdlib.SubtractFunc,
|
|
||||||
"timeadd": stdlib.TimeAddFunc,
|
|
||||||
"title": stdlib.TitleFunc,
|
|
||||||
"trim": stdlib.TrimFunc,
|
|
||||||
"trimprefix": stdlib.TrimPrefixFunc,
|
|
||||||
"trimspace": stdlib.TrimSpaceFunc,
|
|
||||||
"trimsuffix": stdlib.TrimSuffixFunc,
|
|
||||||
"upper": stdlib.UpperFunc,
|
|
||||||
"values": stdlib.ValuesFunc,
|
|
||||||
"zipmap": stdlib.ZipmapFunc,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Used in the first pass of decoding instead of the Config struct to disallow
|
|
||||||
// interpolation while parsing variable blocks.
|
|
||||||
type staticConfig struct {
|
|
||||||
Variables []*Variable `hcl:"variable,block"`
|
|
||||||
Remain hcl.Body `hcl:",remain"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseHCL(dt []byte, fn string) (_ *Config, err error) {
|
|
||||||
if strings.HasSuffix(fn, ".json") || strings.HasSuffix(fn, ".hcl") {
|
|
||||||
return parseHCL(dt, fn)
|
|
||||||
}
|
|
||||||
cfg, err := parseHCL(dt, fn+".hcl")
|
|
||||||
if err != nil {
|
|
||||||
cfg2, err2 := parseHCL(dt, fn+".json")
|
|
||||||
if err2 == nil {
|
|
||||||
return cfg2, nil
|
|
||||||
}
|
}
|
||||||
|
return f, true, err
|
||||||
}
|
}
|
||||||
return cfg, err
|
if strings.HasSuffix(fn, ".hcl") {
|
||||||
}
|
f, diags := hclparse.NewParser().ParseHCL(dt, fn)
|
||||||
|
if diags.HasErrors() {
|
||||||
func parseHCL(dt []byte, fn string) (_ *Config, err error) {
|
err = diags
|
||||||
defer func() {
|
|
||||||
err = formatHCLError(dt, err)
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Decode user defined functions, first parsing as hcl and falling back to
|
|
||||||
// json, returning errors based on the file suffix.
|
|
||||||
file, hcldiags := hclsyntax.ParseConfig(dt, fn, hcl.Pos{Line: 1, Column: 1})
|
|
||||||
if hcldiags.HasErrors() {
|
|
||||||
var jsondiags hcl.Diagnostics
|
|
||||||
file, jsondiags = json.Parse(dt, fn)
|
|
||||||
if jsondiags.HasErrors() {
|
|
||||||
fnl := strings.ToLower(fn)
|
|
||||||
if strings.HasSuffix(fnl, ".json") {
|
|
||||||
return nil, jsondiags
|
|
||||||
} else {
|
|
||||||
return nil, hcldiags
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return f, true, err
|
||||||
}
|
}
|
||||||
|
f, diags := hclparse.NewParser().ParseHCL(dt, fn+".hcl")
|
||||||
userFunctions, _, diags := userfunc.DecodeUserFunctions(file.Body, "function", func() *hcl.EvalContext {
|
|
||||||
return &hcl.EvalContext{
|
|
||||||
Functions: stdlibFunctions,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if diags.HasErrors() {
|
if diags.HasErrors() {
|
||||||
return nil, diags
|
f, diags2 := hclparse.NewParser().ParseJSON(dt, fn+".json")
|
||||||
}
|
if !diags2.HasErrors() {
|
||||||
|
return f, true, nil
|
||||||
var sc staticConfig
|
|
||||||
|
|
||||||
// Decode only variable blocks without interpolation.
|
|
||||||
if err := hclsimple.Decode(fn, dt, nil, &sc); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set all variables to their default value if defined.
|
|
||||||
variables := make(map[string]cty.Value)
|
|
||||||
for _, variable := range sc.Variables {
|
|
||||||
variables[variable.Name] = cty.StringVal(variable.Default)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 nil, false, diags
|
||||||
}
|
}
|
||||||
|
return f, true, nil
|
||||||
functions := make(map[string]function.Function)
|
|
||||||
for k, v := range stdlibFunctions {
|
|
||||||
functions[k] = v
|
|
||||||
}
|
|
||||||
for k, v := range userFunctions {
|
|
||||||
functions[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatHCLError(dt []byte, err error) error {
|
func formatHCLError(err error, files []File) error {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -207,6 +49,13 @@ func formatHCLError(dt []byte, err error) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if d.Subject != nil {
|
if d.Subject != nil {
|
||||||
|
var dt []byte
|
||||||
|
for _, f := range files {
|
||||||
|
if d.Subject.Filename == f.Name {
|
||||||
|
dt = f.Data
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
src := errdefs.Source{
|
src := errdefs.Source{
|
||||||
Info: &pb.SourceInfo{
|
Info: &pb.SourceInfo{
|
||||||
Filename: d.Subject.Filename,
|
Filename: d.Subject.Filename,
|
||||||
|
|||||||
733
bake/hcl_test.go
733
bake/hcl_test.go
@@ -7,11 +7,9 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseHCL(t *testing.T) {
|
func TestHCLBasic(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
dt := []byte(`
|
||||||
t.Run("Basic", func(t *testing.T) {
|
|
||||||
dt := []byte(`
|
|
||||||
group "default" {
|
group "default" {
|
||||||
targets = ["db", "webapp"]
|
targets = ["db", "webapp"]
|
||||||
}
|
}
|
||||||
@@ -44,32 +42,31 @@ func TestParseHCL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseHCL(dt, "docker-bake.hcl")
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(c.Groups))
|
||||||
|
require.Equal(t, "default", c.Groups[0].Name)
|
||||||
|
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Groups))
|
require.Equal(t, 4, len(c.Targets))
|
||||||
require.Equal(t, "default", c.Groups[0].Name)
|
require.Equal(t, c.Targets[0].Name, "db")
|
||||||
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
require.Equal(t, "./db", *c.Targets[0].Context)
|
||||||
|
|
||||||
require.Equal(t, 4, len(c.Targets))
|
require.Equal(t, c.Targets[1].Name, "webapp")
|
||||||
require.Equal(t, c.Targets[0].Name, "db")
|
require.Equal(t, 1, len(c.Targets[1].Args))
|
||||||
require.Equal(t, "./db", *c.Targets[0].Context)
|
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
||||||
|
|
||||||
require.Equal(t, c.Targets[1].Name, "webapp")
|
require.Equal(t, c.Targets[2].Name, "cross")
|
||||||
require.Equal(t, 1, len(c.Targets[1].Args))
|
require.Equal(t, 2, len(c.Targets[2].Platforms))
|
||||||
require.Equal(t, "123", c.Targets[1].Args["buildno"])
|
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[2].Platforms)
|
||||||
|
|
||||||
require.Equal(t, c.Targets[2].Name, "cross")
|
require.Equal(t, c.Targets[3].Name, "webapp-plus")
|
||||||
require.Equal(t, 2, len(c.Targets[2].Platforms))
|
require.Equal(t, 1, len(c.Targets[3].Args))
|
||||||
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[2].Platforms)
|
require.Equal(t, map[string]string{"IAMCROSS": "true"}, c.Targets[3].Args)
|
||||||
|
}
|
||||||
|
|
||||||
require.Equal(t, c.Targets[3].Name, "webapp-plus")
|
func TestHCLBasicInJSON(t *testing.T) {
|
||||||
require.Equal(t, 1, len(c.Targets[3].Args))
|
dt := []byte(`
|
||||||
require.Equal(t, map[string]string{"IAMCROSS": "true"}, c.Targets[3].Args)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("BasicInJSON", func(t *testing.T) {
|
|
||||||
dt := []byte(`
|
|
||||||
{
|
{
|
||||||
"group": {
|
"group": {
|
||||||
"default": {
|
"default": {
|
||||||
@@ -104,32 +101,32 @@ func TestParseHCL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseHCL(dt, "docker-bake.json")
|
c, err := ParseFile(dt, "docker-bake.json")
|
||||||
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, "default", c.Groups[0].Name)
|
require.Equal(t, "default", c.Groups[0].Name)
|
||||||
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
require.Equal(t, []string{"db", "webapp"}, c.Groups[0].Targets)
|
||||||
|
|
||||||
require.Equal(t, 4, len(c.Targets))
|
require.Equal(t, 4, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "db")
|
require.Equal(t, c.Targets[0].Name, "db")
|
||||||
require.Equal(t, "./db", *c.Targets[0].Context)
|
require.Equal(t, "./db", *c.Targets[0].Context)
|
||||||
|
|
||||||
require.Equal(t, c.Targets[1].Name, "webapp")
|
require.Equal(t, c.Targets[1].Name, "webapp")
|
||||||
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, "123", c.Targets[1].Args["buildno"])
|
||||||
|
|
||||||
require.Equal(t, c.Targets[2].Name, "cross")
|
require.Equal(t, c.Targets[2].Name, "cross")
|
||||||
require.Equal(t, 2, len(c.Targets[2].Platforms))
|
require.Equal(t, 2, len(c.Targets[2].Platforms))
|
||||||
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[2].Platforms)
|
require.Equal(t, []string{"linux/amd64", "linux/arm64"}, c.Targets[2].Platforms)
|
||||||
|
|
||||||
require.Equal(t, c.Targets[3].Name, "webapp-plus")
|
require.Equal(t, c.Targets[3].Name, "webapp-plus")
|
||||||
require.Equal(t, 1, len(c.Targets[3].Args))
|
require.Equal(t, 1, len(c.Targets[3].Args))
|
||||||
require.Equal(t, map[string]string{"IAMCROSS": "true"}, c.Targets[3].Args)
|
require.Equal(t, map[string]string{"IAMCROSS": "true"}, c.Targets[3].Args)
|
||||||
})
|
}
|
||||||
|
|
||||||
t.Run("WithFunctions", func(t *testing.T) {
|
func TestHCLWithFunctions(t *testing.T) {
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
group "default" {
|
group "default" {
|
||||||
targets = ["webapp"]
|
targets = ["webapp"]
|
||||||
}
|
}
|
||||||
@@ -141,20 +138,20 @@ func TestParseHCL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseHCL(dt, "docker-bake.hcl")
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
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, "default", c.Groups[0].Name)
|
require.Equal(t, "default", c.Groups[0].Name)
|
||||||
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||||
require.Equal(t, "124", c.Targets[0].Args["buildno"])
|
require.Equal(t, "124", c.Targets[0].Args["buildno"])
|
||||||
})
|
}
|
||||||
|
|
||||||
t.Run("WithUserDefinedFunctions", func(t *testing.T) {
|
func TestHCLWithUserDefinedFunctions(t *testing.T) {
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
function "increment" {
|
function "increment" {
|
||||||
params = [number]
|
params = [number]
|
||||||
result = number + 1
|
result = number + 1
|
||||||
@@ -171,20 +168,20 @@ func TestParseHCL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseHCL(dt, "docker-bake.hcl")
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
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, "default", c.Groups[0].Name)
|
require.Equal(t, "default", c.Groups[0].Name)
|
||||||
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||||
require.Equal(t, "124", c.Targets[0].Args["buildno"])
|
require.Equal(t, "124", c.Targets[0].Args["buildno"])
|
||||||
})
|
}
|
||||||
|
|
||||||
t.Run("WithVariables", func(t *testing.T) {
|
func TestHCLWithVariables(t *testing.T) {
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
variable "BUILD_NUMBER" {
|
variable "BUILD_NUMBER" {
|
||||||
default = "123"
|
default = "123"
|
||||||
}
|
}
|
||||||
@@ -200,54 +197,588 @@ func TestParseHCL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
c, err := ParseHCL(dt, "docker-bake.hcl")
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
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, "default", c.Groups[0].Name)
|
require.Equal(t, "default", c.Groups[0].Name)
|
||||||
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||||
require.Equal(t, "123", c.Targets[0].Args["buildno"])
|
require.Equal(t, "123", c.Targets[0].Args["buildno"])
|
||||||
|
|
||||||
os.Setenv("BUILD_NUMBER", "456")
|
os.Setenv("BUILD_NUMBER", "456")
|
||||||
|
|
||||||
c, err = ParseHCL(dt, "docker-bake.hcl")
|
c, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
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, "default", c.Groups[0].Name)
|
require.Equal(t, "default", c.Groups[0].Name)
|
||||||
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
require.Equal(t, []string{"webapp"}, c.Groups[0].Targets)
|
||||||
|
|
||||||
require.Equal(t, 1, len(c.Targets))
|
require.Equal(t, 1, len(c.Targets))
|
||||||
require.Equal(t, c.Targets[0].Name, "webapp")
|
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||||
require.Equal(t, "456", c.Targets[0].Args["buildno"])
|
require.Equal(t, "456", c.Targets[0].Args["buildno"])
|
||||||
})
|
}
|
||||||
|
|
||||||
t.Run("WithIncorrectVariables", func(t *testing.T) {
|
func TestHCLWithVariablesInFunctions(t *testing.T) {
|
||||||
dt := []byte(`
|
dt := []byte(`
|
||||||
variable "DEFAULT_BUILD_NUMBER" {
|
variable "REPO" {
|
||||||
default = "1"
|
default = "user/repo"
|
||||||
}
|
}
|
||||||
|
function "tag" {
|
||||||
variable "BUILD_NUMBER" {
|
params = [tag]
|
||||||
default = "${DEFAULT_BUILD_NUMBER}"
|
result = ["${REPO}:${tag}"]
|
||||||
}
|
|
||||||
|
|
||||||
group "default" {
|
|
||||||
targets = ["webapp"]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
target "webapp" {
|
target "webapp" {
|
||||||
|
tags = tag("v1")
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||||
|
require.Equal(t, []string{"user/repo:v1"}, c.Targets[0].Tags)
|
||||||
|
|
||||||
|
os.Setenv("REPO", "docker/buildx")
|
||||||
|
|
||||||
|
c, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "webapp")
|
||||||
|
require.Equal(t, []string{"docker/buildx:v1"}, c.Targets[0].Tags)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLMultiFileSharedVariables(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
variable "FOO" {
|
||||||
|
default = "abc"
|
||||||
|
}
|
||||||
|
target "app" {
|
||||||
args = {
|
args = {
|
||||||
buildno = "${BUILD_NUMBER}"
|
v1 = "pre-${FOO}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
dt2 := []byte(`
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v2 = "${FOO}-post"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`)
|
`)
|
||||||
|
|
||||||
_, err := ParseHCL(dt, "docker-bake.hcl")
|
c, err := ParseFiles([]File{
|
||||||
require.Error(t, err)
|
{Data: dt, Name: "c1.hcl"},
|
||||||
require.Contains(t, err.Error(), "docker-bake.hcl:7,17-37: Variables not allowed; Variables may not be used here.")
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
})
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "pre-abc", c.Targets[0].Args["v1"])
|
||||||
|
require.Equal(t, "abc-post", c.Targets[0].Args["v2"])
|
||||||
|
|
||||||
|
os.Setenv("FOO", "def")
|
||||||
|
|
||||||
|
c, err = ParseFiles([]File{
|
||||||
|
{Data: dt, Name: "c1.hcl"},
|
||||||
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "pre-def", c.Targets[0].Args["v1"])
|
||||||
|
require.Equal(t, "def-post", c.Targets[0].Args["v2"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLVarsWithVars(t *testing.T) {
|
||||||
|
os.Unsetenv("FOO")
|
||||||
|
dt := []byte(`
|
||||||
|
variable "FOO" {
|
||||||
|
default = upper("${BASE}def")
|
||||||
|
}
|
||||||
|
variable "BAR" {
|
||||||
|
default = "-${FOO}-"
|
||||||
|
}
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v1 = "pre-${BAR}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
dt2 := []byte(`
|
||||||
|
variable "BASE" {
|
||||||
|
default = "abc"
|
||||||
|
}
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v2 = "${FOO}-post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFiles([]File{
|
||||||
|
{Data: dt, Name: "c1.hcl"},
|
||||||
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "pre--ABCDEF-", c.Targets[0].Args["v1"])
|
||||||
|
require.Equal(t, "ABCDEF-post", c.Targets[0].Args["v2"])
|
||||||
|
|
||||||
|
os.Setenv("BASE", "new")
|
||||||
|
|
||||||
|
c, err = ParseFiles([]File{
|
||||||
|
{Data: dt, Name: "c1.hcl"},
|
||||||
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "pre--NEWDEF-", c.Targets[0].Args["v1"])
|
||||||
|
require.Equal(t, "NEWDEF-post", c.Targets[0].Args["v2"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLTypedVariables(t *testing.T) {
|
||||||
|
os.Unsetenv("FOO")
|
||||||
|
dt := []byte(`
|
||||||
|
variable "FOO" {
|
||||||
|
default = 3
|
||||||
|
}
|
||||||
|
variable "IS_FOO" {
|
||||||
|
default = true
|
||||||
|
}
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v1 = FOO > 5 ? "higher" : "lower"
|
||||||
|
v2 = IS_FOO ? "yes" : "no"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "lower", c.Targets[0].Args["v1"])
|
||||||
|
require.Equal(t, "yes", c.Targets[0].Args["v2"])
|
||||||
|
|
||||||
|
os.Setenv("FOO", "5.1")
|
||||||
|
os.Setenv("IS_FOO", "0")
|
||||||
|
|
||||||
|
c, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "higher", c.Targets[0].Args["v1"])
|
||||||
|
require.Equal(t, "no", c.Targets[0].Args["v2"])
|
||||||
|
|
||||||
|
os.Setenv("FOO", "NaN")
|
||||||
|
_, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "failed to parse FOO as number")
|
||||||
|
|
||||||
|
os.Setenv("FOO", "0")
|
||||||
|
os.Setenv("IS_FOO", "maybe")
|
||||||
|
|
||||||
|
_, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "failed to parse IS_FOO as bool")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLVariableCycle(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
variable "FOO" {
|
||||||
|
default = BAR
|
||||||
|
}
|
||||||
|
variable "FOO2" {
|
||||||
|
default = FOO
|
||||||
|
}
|
||||||
|
variable "BAR" {
|
||||||
|
default = FOO
|
||||||
|
}
|
||||||
|
target "app" {}
|
||||||
|
`)
|
||||||
|
|
||||||
|
_, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "variable cycle not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLAttrs(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
FOO="abc"
|
||||||
|
BAR="attr-${FOO}def"
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
"v1": BAR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "attr-abcdef", c.Targets[0].Args["v1"])
|
||||||
|
|
||||||
|
// env does not apply if no variable
|
||||||
|
os.Setenv("FOO", "bar")
|
||||||
|
c, err = ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "attr-abcdef", c.Targets[0].Args["v1"])
|
||||||
|
// attr-multifile
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLAttrsCustomType(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
platforms=["linux/arm64", "linux/amd64"]
|
||||||
|
target "app" {
|
||||||
|
platforms = platforms
|
||||||
|
args = {
|
||||||
|
"v1": platforms[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, []string{"linux/arm64", "linux/amd64"}, c.Targets[0].Platforms)
|
||||||
|
require.Equal(t, "linux/arm64", c.Targets[0].Args["v1"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLMultiFileAttrs(t *testing.T) {
|
||||||
|
os.Unsetenv("FOO")
|
||||||
|
dt := []byte(`
|
||||||
|
variable "FOO" {
|
||||||
|
default = "abc"
|
||||||
|
}
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v1 = "pre-${FOO}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
dt2 := []byte(`
|
||||||
|
FOO="def"
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFiles([]File{
|
||||||
|
{Data: dt, Name: "c1.hcl"},
|
||||||
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "pre-def", c.Targets[0].Args["v1"])
|
||||||
|
|
||||||
|
os.Setenv("FOO", "ghi")
|
||||||
|
|
||||||
|
c, err = ParseFiles([]File{
|
||||||
|
{Data: dt, Name: "c1.hcl"},
|
||||||
|
{Data: dt2, Name: "c2.hcl"},
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "pre-ghi", c.Targets[0].Args["v1"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONAttributes(t *testing.T) {
|
||||||
|
dt := []byte(`{"FOO": "abc", "variable": {"BAR": {"default": "def"}}, "target": { "app": { "args": {"v1": "pre-${FOO}-${BAR}"}} } }`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "pre-abc-def", c.Targets[0].Args["v1"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONFunctions(t *testing.T) {
|
||||||
|
dt := []byte(`{
|
||||||
|
"FOO": "abc",
|
||||||
|
"function": {
|
||||||
|
"myfunc": {
|
||||||
|
"params": ["inp"],
|
||||||
|
"result": "<${upper(inp)}-${FOO}>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"app": {
|
||||||
|
"args": {
|
||||||
|
"v1": "pre-${myfunc(\"foo\")}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "pre-<FOO-abc>", c.Targets[0].Args["v1"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLFunctionInAttr(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
function "brace" {
|
||||||
|
params = [inp]
|
||||||
|
result = "[${inp}]"
|
||||||
|
}
|
||||||
|
function "myupper" {
|
||||||
|
params = [val]
|
||||||
|
result = "${upper(val)} <> ${brace(v2)}"
|
||||||
|
}
|
||||||
|
|
||||||
|
v1=myupper("foo")
|
||||||
|
v2=lower("BAZ")
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
"v1": v1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "FOO <> [baz]", c.Targets[0].Args["v1"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLCombineCompose(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
target "app" {
|
||||||
|
context = "dir"
|
||||||
|
args = {
|
||||||
|
v1 = "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
dt2 := []byte(`
|
||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile-alternate
|
||||||
|
args:
|
||||||
|
v2: "bar"
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFiles([]File{
|
||||||
|
{Data: dt, Name: "c1.hcl"},
|
||||||
|
{Data: dt2, Name: "c2.yml"},
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "foo", c.Targets[0].Args["v1"])
|
||||||
|
require.Equal(t, "bar", c.Targets[0].Args["v2"])
|
||||||
|
require.Equal(t, "dir", *c.Targets[0].Context)
|
||||||
|
require.Equal(t, "Dockerfile-alternate", *c.Targets[0].Dockerfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHCLBuiltinVars(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
target "app" {
|
||||||
|
context = BAKE_CMD_CONTEXT
|
||||||
|
dockerfile = "test"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
c, err := ParseFiles([]File{
|
||||||
|
{Data: dt, Name: "c1.hcl"},
|
||||||
|
}, map[string]string{
|
||||||
|
"BAKE_CMD_CONTEXT": "foo",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Targets))
|
||||||
|
require.Equal(t, c.Targets[0].Name, "app")
|
||||||
|
require.Equal(t, "foo", *c.Targets[0].Context)
|
||||||
|
require.Equal(t, "test", *c.Targets[0].Dockerfile)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCombineHCLAndJSONTargets(t *testing.T) {
|
||||||
|
c, err := ParseFiles([]File{
|
||||||
|
{
|
||||||
|
Name: "docker-bake.hcl",
|
||||||
|
Data: []byte(`
|
||||||
|
group "default" {
|
||||||
|
targets = ["a"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "metadata-a" {}
|
||||||
|
target "metadata-b" {}
|
||||||
|
|
||||||
|
target "a" {
|
||||||
|
inherits = ["metadata-a"]
|
||||||
|
context = "."
|
||||||
|
target = "a"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "b" {
|
||||||
|
inherits = ["metadata-b"]
|
||||||
|
context = "."
|
||||||
|
target = "b"
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "metadata-a.json",
|
||||||
|
Data: []byte(`
|
||||||
|
{
|
||||||
|
"target": [{
|
||||||
|
"metadata-a": [{
|
||||||
|
"tags": [
|
||||||
|
"app/a:1.0.0",
|
||||||
|
"app/a:latest"
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "metadata-b.json",
|
||||||
|
Data: []byte(`
|
||||||
|
{
|
||||||
|
"target": [{
|
||||||
|
"metadata-b": [{
|
||||||
|
"tags": [
|
||||||
|
"app/b:1.0.0",
|
||||||
|
"app/b:latest"
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}]
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Groups))
|
||||||
|
require.Equal(t, "default", c.Groups[0].Name)
|
||||||
|
require.Equal(t, []string{"a"}, c.Groups[0].Targets)
|
||||||
|
|
||||||
|
require.Equal(t, 4, len(c.Targets))
|
||||||
|
|
||||||
|
require.Equal(t, c.Targets[0].Name, "metadata-a")
|
||||||
|
require.Equal(t, []string{"app/a:1.0.0", "app/a:latest"}, c.Targets[0].Tags)
|
||||||
|
|
||||||
|
require.Equal(t, c.Targets[1].Name, "metadata-b")
|
||||||
|
require.Equal(t, []string{"app/b:1.0.0", "app/b:latest"}, c.Targets[1].Tags)
|
||||||
|
|
||||||
|
require.Equal(t, c.Targets[2].Name, "a")
|
||||||
|
require.Equal(t, ".", *c.Targets[2].Context)
|
||||||
|
require.Equal(t, "a", *c.Targets[2].Target)
|
||||||
|
|
||||||
|
require.Equal(t, c.Targets[3].Name, "b")
|
||||||
|
require.Equal(t, ".", *c.Targets[3].Context)
|
||||||
|
require.Equal(t, "b", *c.Targets[3].Target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCombineHCLAndJSONVars(t *testing.T) {
|
||||||
|
c, err := ParseFiles([]File{
|
||||||
|
{
|
||||||
|
Name: "docker-bake.hcl",
|
||||||
|
Data: []byte(`
|
||||||
|
variable "ABC" {
|
||||||
|
default = "foo"
|
||||||
|
}
|
||||||
|
variable "DEF" {
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
group "default" {
|
||||||
|
targets = ["one"]
|
||||||
|
}
|
||||||
|
target "one" {
|
||||||
|
args = {
|
||||||
|
a = "pre-${ABC}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
target "two" {
|
||||||
|
args = {
|
||||||
|
b = "pre-${DEF}"
|
||||||
|
}
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "foo.json",
|
||||||
|
Data: []byte(`{"variable": {"DEF": {"default": "bar"}}, "target": { "one": { "args": {"a": "pre-${ABC}-${DEF}"}} } }`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "bar.json",
|
||||||
|
Data: []byte(`{"ABC": "ghi", "DEF": "jkl"}`),
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(c.Groups))
|
||||||
|
require.Equal(t, "default", c.Groups[0].Name)
|
||||||
|
require.Equal(t, []string{"one"}, c.Groups[0].Targets)
|
||||||
|
|
||||||
|
require.Equal(t, 2, len(c.Targets))
|
||||||
|
|
||||||
|
require.Equal(t, c.Targets[0].Name, "one")
|
||||||
|
require.Equal(t, map[string]string{"a": "pre-ghi-jkl"}, c.Targets[0].Args)
|
||||||
|
|
||||||
|
require.Equal(t, c.Targets[1].Name, "two")
|
||||||
|
require.Equal(t, map[string]string{"b": "pre-jkl"}, c.Targets[1].Args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyVariableJSON(t *testing.T) {
|
||||||
|
dt := []byte(`{
|
||||||
|
"variable": {
|
||||||
|
"VAR": {}
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
_, err := ParseFile(dt, "docker-bake.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFunctionNoParams(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
function "foo" {
|
||||||
|
result = "bar"
|
||||||
|
}
|
||||||
|
target "foo_target" {
|
||||||
|
args = {
|
||||||
|
test = foo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
_, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFunctionNoResult(t *testing.T) {
|
||||||
|
dt := []byte(`
|
||||||
|
function "foo" {
|
||||||
|
params = ["a"]
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
_, err := ParseFile(dt, "docker-bake.hcl")
|
||||||
|
require.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|||||||
153
bake/hclparser/expr.go
Normal file
153
bake/hclparser/expr.go
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
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, hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid expression",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: exp.Range().Ptr(),
|
||||||
|
Context: 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.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rng := src.FieldByName("SrcRange")
|
||||||
|
if val.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}
|
||||||
|
}
|
||||||
571
bake/hclparser/hclparser.go
Normal file
571
bake/hclparser/hclparser.go
Normal file
@@ -0,0 +1,571 @@
|
|||||||
|
package hclparser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
ectx *hcl.EvalContext
|
||||||
|
|
||||||
|
progress map[string]struct{}
|
||||||
|
progressF map[string]struct{}
|
||||||
|
doneF map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) loadDeps(exp hcl.Expression, exclude map[string]struct{}) hcl.Diagnostics {
|
||||||
|
fns, hcldiags := funcCalls(exp)
|
||||||
|
if hcldiags.HasErrors() {
|
||||||
|
return hcldiags
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fn := range fns {
|
||||||
|
if err := p.resolveFunction(fn); err != nil {
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid expression",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: exp.Range().Ptr(),
|
||||||
|
Context: exp.Range().Ptr(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, v := range exp.Variables() {
|
||||||
|
if _, ok := exclude[v.RootName()]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := p.resolveValue(v.RootName()); err != nil {
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid expression",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: v.SourceRange().Ptr(),
|
||||||
|
Context: v.SourceRange().Ptr(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) resolveFunction(name string) error {
|
||||||
|
if _, ok := p.doneF[name]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
f, ok := p.funcs[name]
|
||||||
|
if !ok {
|
||||||
|
if _, ok := p.ectx.Functions[name]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.Errorf("undefined function %s", name)
|
||||||
|
}
|
||||||
|
if _, ok := p.progressF[name]; ok {
|
||||||
|
return errors.Errorf("function cycle not allowed for %s", name)
|
||||||
|
}
|
||||||
|
p.progressF[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(f.Result.Expr, params); 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.doneF[name] = struct{}{}
|
||||||
|
p.ectx.Functions[name] = v
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *parser) resolveValue(name string) (err error) {
|
||||||
|
if _, ok := p.ectx.Variables[name]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if _, ok := p.progress[name]; ok {
|
||||||
|
return errors.Errorf("variable cycle not allowed for %s", name)
|
||||||
|
}
|
||||||
|
p.progress[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.Errorf("undefined variable %q", name)
|
||||||
|
}
|
||||||
|
def = vr.Default
|
||||||
|
}
|
||||||
|
|
||||||
|
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(def.Expr, nil); diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
vv, diags := def.Expr.Value(p.ectx)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
_, isVar := p.vars[name]
|
||||||
|
|
||||||
|
if envv, ok := p.opt.LookupVar(name); ok && isVar {
|
||||||
|
if 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)
|
||||||
|
v = &vv
|
||||||
|
return nil
|
||||||
|
} else if vv.Type().Equals(cty.String) {
|
||||||
|
vv := cty.StringVal(envv)
|
||||||
|
v = &vv
|
||||||
|
return nil
|
||||||
|
} else if 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))
|
||||||
|
v = &vv
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
// TODO: support lists with csv values
|
||||||
|
return errors.Errorf("unsupported type %s for variable %s", v.Type(), name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = &vv
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Parse(b hcl.Body, opt Opt, val interface{}) 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 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{},
|
||||||
|
|
||||||
|
progress: map[string]struct{}{},
|
||||||
|
progressF: map[string]struct{}{},
|
||||||
|
doneF: map[string]struct{}{},
|
||||||
|
ectx: &hcl.EvalContext{
|
||||||
|
Variables: map[string]cty.Value{},
|
||||||
|
Functions: stdlibFunctions,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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 diags
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks, b, diags := b.PartialContent(defsSchema)
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs, diags := b.JustAttributes()
|
||||||
|
if diags.HasErrors() {
|
||||||
|
if d := removeAttributesDiags(diags, reserved, p.vars); len(d) > 0 {
|
||||||
|
return 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(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range p.attrs {
|
||||||
|
if err := p.resolveValue(k); err != nil {
|
||||||
|
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid attribute",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: &p.attrs[k].Range,
|
||||||
|
Context: &p.attrs[k].Range,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range p.vars {
|
||||||
|
if err := p.resolveValue(k); err != nil {
|
||||||
|
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
r := p.vars[k].Body.MissingItemRange()
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid value",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: &r,
|
||||||
|
Context: &r,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for k := range p.funcs {
|
||||||
|
if err := p.resolveFunction(k); err != nil {
|
||||||
|
if diags, ok := err.(hcl.Diagnostics); ok {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
var subject *hcl.Range
|
||||||
|
var context *hcl.Range
|
||||||
|
if p.funcs[k].Params != nil {
|
||||||
|
subject = &p.funcs[k].Params.Range
|
||||||
|
context = subject
|
||||||
|
} else {
|
||||||
|
for _, block := range blocks.Blocks {
|
||||||
|
if block.Type == "function" && len(block.Labels) == 1 && block.Labels[0] == k {
|
||||||
|
subject = &block.LabelRanges[0]
|
||||||
|
context = &block.DefRange
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid function",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: subject,
|
||||||
|
Context: context,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, a := range content.Attributes {
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid attribute",
|
||||||
|
Detail: "global attributes currently not supported",
|
||||||
|
Subject: &a.Range,
|
||||||
|
Context: &a.Range,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]map[string][]*hcl.Block{}
|
||||||
|
for _, b := range content.Blocks {
|
||||||
|
if len(b.Labels) == 0 || len(b.Labels) > 1 {
|
||||||
|
return 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 := m[b.Type]
|
||||||
|
if !ok {
|
||||||
|
bm = map[string][]*hcl.Block{}
|
||||||
|
m[b.Type] = bm
|
||||||
|
}
|
||||||
|
|
||||||
|
lbl := b.Labels[0]
|
||||||
|
bm[lbl] = append(bm[lbl], b)
|
||||||
|
}
|
||||||
|
|
||||||
|
vt := reflect.ValueOf(val).Elem().Type()
|
||||||
|
numFields := vt.NumField()
|
||||||
|
|
||||||
|
type value struct {
|
||||||
|
reflect.Value
|
||||||
|
idx int
|
||||||
|
}
|
||||||
|
type field struct {
|
||||||
|
idx int
|
||||||
|
typ reflect.Type
|
||||||
|
values map[string]value
|
||||||
|
}
|
||||||
|
types := map[string]field{}
|
||||||
|
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
tags := strings.Split(vt.Field(i).Tag.Get("hcl"), ",")
|
||||||
|
|
||||||
|
types[tags[0]] = field{
|
||||||
|
idx: i,
|
||||||
|
typ: vt.Field(i).Type,
|
||||||
|
values: make(map[string]value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diags = hcl.Diagnostics{}
|
||||||
|
for _, b := range content.Blocks {
|
||||||
|
v := reflect.ValueOf(val)
|
||||||
|
|
||||||
|
t, ok := types[b.Type]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := reflect.New(t.typ.Elem().Elem())
|
||||||
|
diag := gohcl.DecodeBody(b.Body, p.ectx, vv.Interface())
|
||||||
|
if diag.HasErrors() {
|
||||||
|
diags = append(diags, diag...)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := opt.ValidateLabel(b.Labels[0]); err != nil {
|
||||||
|
return hcl.Diagnostics{
|
||||||
|
&hcl.Diagnostic{
|
||||||
|
Severity: hcl.DiagError,
|
||||||
|
Summary: "Invalid name",
|
||||||
|
Detail: err.Error(),
|
||||||
|
Subject: &b.LabelRanges[0],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lblIndex := setLabel(vv, b.Labels[0])
|
||||||
|
|
||||||
|
oldValue, exists := t.values[b.Labels[0]]
|
||||||
|
if !exists && lblIndex != -1 {
|
||||||
|
if v.Elem().Field(t.idx).Type().Kind() == reflect.Slice {
|
||||||
|
for i := 0; i < v.Elem().Field(t.idx).Len(); i++ {
|
||||||
|
if b.Labels[0] == 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[b.Labels[0]] = value{Value: vv, idx: slice.Len()}
|
||||||
|
v.Elem().Field(t.idx).Set(reflect.Append(slice, vv))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if diags.HasErrors() {
|
||||||
|
return diags
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setLabel(v reflect.Value, lbl string) int {
|
||||||
|
// cache field index?
|
||||||
|
numFields := v.Elem().Type().NumField()
|
||||||
|
for i := 0; i < numFields; i++ {
|
||||||
|
for _, t := range strings.Split(v.Elem().Type().Field(i).Tag.Get("hcl"), ",") {
|
||||||
|
if t == "label" {
|
||||||
|
v.Elem().Field(i).Set(reflect.ValueOf(lbl))
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeAttributesDiags(diags hcl.Diagnostics, reserved map[string]struct{}, vars map[string]*variable) 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}(d); !fout {
|
||||||
|
fdiags = append(fdiags, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fdiags
|
||||||
|
}
|
||||||
126
bake/hclparser/stdlib.go
Normal file
126
bake/hclparser/stdlib.go
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
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,
|
||||||
|
"csvdecode": stdlib.CSVDecodeFunc,
|
||||||
|
"coalesce": stdlib.CoalesceFunc,
|
||||||
|
"coalescelist": stdlib.CoalesceListFunc,
|
||||||
|
"compact": stdlib.CompactFunc,
|
||||||
|
"concat": stdlib.ConcatFunc,
|
||||||
|
"contains": stdlib.ContainsFunc,
|
||||||
|
"convert": typeexpr.ConvertFunc,
|
||||||
|
"distinct": stdlib.DistinctFunc,
|
||||||
|
"divide": stdlib.DivideFunc,
|
||||||
|
"element": stdlib.ElementFunc,
|
||||||
|
"equal": stdlib.EqualFunc,
|
||||||
|
"flatten": stdlib.FlattenFunc,
|
||||||
|
"floor": stdlib.FloorFunc,
|
||||||
|
"formatdate": stdlib.FormatDateFunc,
|
||||||
|
"format": stdlib.FormatFunc,
|
||||||
|
"formatlist": stdlib.FormatListFunc,
|
||||||
|
"greaterthan": stdlib.GreaterThanFunc,
|
||||||
|
"greaterthanorequalto": stdlib.GreaterThanOrEqualToFunc,
|
||||||
|
"hasindex": stdlib.HasIndexFunc,
|
||||||
|
"indent": stdlib.IndentFunc,
|
||||||
|
"index": stdlib.IndexFunc,
|
||||||
|
"int": stdlib.IntFunc,
|
||||||
|
"jsondecode": stdlib.JSONDecodeFunc,
|
||||||
|
"jsonencode": stdlib.JSONEncodeFunc,
|
||||||
|
"keys": stdlib.KeysFunc,
|
||||||
|
"join": stdlib.JoinFunc,
|
||||||
|
"length": stdlib.LengthFunc,
|
||||||
|
"lessthan": stdlib.LessThanFunc,
|
||||||
|
"lessthanorequalto": stdlib.LessThanOrEqualToFunc,
|
||||||
|
"log": stdlib.LogFunc,
|
||||||
|
"lookup": stdlib.LookupFunc,
|
||||||
|
"lower": stdlib.LowerFunc,
|
||||||
|
"max": stdlib.MaxFunc,
|
||||||
|
"md5": crypto.Md5Func,
|
||||||
|
"merge": stdlib.MergeFunc,
|
||||||
|
"min": stdlib.MinFunc,
|
||||||
|
"modulo": stdlib.ModuloFunc,
|
||||||
|
"multiply": stdlib.MultiplyFunc,
|
||||||
|
"negate": stdlib.NegateFunc,
|
||||||
|
"notequal": stdlib.NotEqualFunc,
|
||||||
|
"not": stdlib.NotFunc,
|
||||||
|
"or": stdlib.OrFunc,
|
||||||
|
"parseint": stdlib.ParseIntFunc,
|
||||||
|
"pow": stdlib.PowFunc,
|
||||||
|
"range": stdlib.RangeFunc,
|
||||||
|
"regexall": stdlib.RegexAllFunc,
|
||||||
|
"regex": stdlib.RegexFunc,
|
||||||
|
"regex_replace": stdlib.RegexReplaceFunc,
|
||||||
|
"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
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -21,9 +21,10 @@ type Input struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func ReadRemoteFiles(ctx context.Context, dis []build.DriverInfo, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
|
func ReadRemoteFiles(ctx context.Context, dis []build.DriverInfo, url string, names []string, pw progress.Writer) ([]File, *Input, error) {
|
||||||
st, filename, ok := detectHttpContext(url)
|
var filename string
|
||||||
|
st, ok := detectGitContext(url)
|
||||||
if !ok {
|
if !ok {
|
||||||
st, ok = detectGitContext(url)
|
st, filename, ok = detectHTTPContext(url)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil, errors.Errorf("not url context")
|
return nil, nil, errors.Errorf("not url context")
|
||||||
}
|
}
|
||||||
@@ -43,7 +44,7 @@ func ReadRemoteFiles(ctx context.Context, dis []build.DriverInfo, url string, na
|
|||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := driver.Boot(ctx, di.Driver, pw)
|
c, err := driver.Boot(ctx, ctx, di.Driver, pw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@@ -83,7 +84,7 @@ func ReadRemoteFiles(ctx context.Context, dis []build.DriverInfo, url string, na
|
|||||||
}
|
}
|
||||||
|
|
||||||
func IsRemoteURL(url string) bool {
|
func IsRemoteURL(url string) bool {
|
||||||
if _, _, ok := detectHttpContext(url); ok {
|
if _, _, ok := detectHTTPContext(url); ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if _, ok := detectGitContext(url); ok {
|
if _, ok := detectGitContext(url); ok {
|
||||||
@@ -92,7 +93,7 @@ func IsRemoteURL(url string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func detectHttpContext(url string) (*llb.State, string, bool) {
|
func detectHTTPContext(url string) (*llb.State, string, bool) {
|
||||||
if httpPrefix.MatchString(url) {
|
if httpPrefix.MatchString(url) {
|
||||||
httpContext := llb.HTTP(url, llb.Filename("context"), llb.WithCustomName("[internal] load remote build context"))
|
httpContext := llb.HTTP(url, llb.Filename("context"), llb.WithCustomName("[internal] load remote build context"))
|
||||||
return &httpContext, "context", true
|
return &httpContext, "context", true
|
||||||
|
|||||||
773
build/build.go
773
build/build.go
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@ package build
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func createTempDockerfileFromURL(ctx context.Context, d driver.Driver, url string, pw progress.Writer) (string, error) {
|
func createTempDockerfileFromURL(ctx context.Context, d driver.Driver, url string, pw progress.Writer) (string, error) {
|
||||||
c, err := driver.Boot(ctx, d, pw)
|
c, err := driver.Boot(ctx, ctx, d, pw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -53,11 +53,11 @@ func createTempDockerfileFromURL(ctx context.Context, d driver.Driver, url strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dir, err := ioutil.TempDir("", "buildx")
|
dir, err := os.MkdirTemp("", "buildx")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := ioutil.WriteFile(filepath.Join(dir, "Dockerfile"), dt, 0600); err != nil {
|
if err := os.WriteFile(filepath.Join(dir, "Dockerfile"), dt, 0600); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
out = dir
|
out = dir
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/cli/opts"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,3 +54,15 @@ func toBuildkitExtraHosts(inp []string) (string, error) {
|
|||||||
}
|
}
|
||||||
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import (
|
|||||||
"github.com/moby/buildkit/solver/errdefs"
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
"github.com/moby/buildkit/util/stack"
|
"github.com/moby/buildkit/util/stack"
|
||||||
|
|
||||||
// FIXME: "k8s.io/client-go/plugin/pkg/client/auth/azure" is excluded because of compilation error
|
_ "k8s.io/client-go/plugin/pkg/client/auth/azure"
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
|
_ "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"
|
_ "k8s.io/client-go/plugin/pkg/client/auth/openstack"
|
||||||
@@ -24,69 +24,67 @@ import (
|
|||||||
_ "github.com/docker/buildx/driver/docker"
|
_ "github.com/docker/buildx/driver/docker"
|
||||||
_ "github.com/docker/buildx/driver/docker-container"
|
_ "github.com/docker/buildx/driver/docker-container"
|
||||||
_ "github.com/docker/buildx/driver/kubernetes"
|
_ "github.com/docker/buildx/driver/kubernetes"
|
||||||
|
_ "github.com/docker/buildx/driver/remote"
|
||||||
)
|
)
|
||||||
|
|
||||||
var experimental string
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
seed.WithTimeAndRand()
|
seed.WithTimeAndRand()
|
||||||
stack.SetVersionInfo(version.Version, version.Revision)
|
stack.SetVersionInfo(version.Version, version.Revision)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func runStandalone(cmd *command.DockerCli) error {
|
||||||
if os.Getenv("DOCKER_CLI_PLUGIN_ORIGINAL_CLI_COMMAND") == "" {
|
if err := cmd.Initialize(cliflags.NewClientOptions()); err != nil {
|
||||||
if len(os.Args) < 2 || os.Args[1] != manager.MetadataSubcommandName {
|
return err
|
||||||
dockerCli, err := command.NewDockerCli()
|
|
||||||
if err != nil {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
rootCmd := commands.NewRootCmd(os.Args[0], false, cmd)
|
||||||
|
return rootCmd.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
dockerCli, err := command.NewDockerCli()
|
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() {
|
||||||
|
cmd, err := command.NewDockerCli()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
p := commands.NewRootCmd("buildx", true, dockerCli)
|
if plugin.RunningStandalone() {
|
||||||
meta := manager.Metadata{
|
err = runStandalone(cmd)
|
||||||
SchemaVersion: "0.1.0",
|
} else {
|
||||||
Vendor: "Docker Inc.",
|
err = runPlugin(cmd)
|
||||||
Version: version.Version,
|
}
|
||||||
Experimental: experimental != "",
|
if err == nil {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := plugin.RunPlugin(dockerCli, p, meta); err != nil {
|
if sterr, ok := err.(cli.StatusError); ok {
|
||||||
if sterr, ok := err.(cli.StatusError); ok {
|
if sterr.Status != "" {
|
||||||
if sterr.Status != "" {
|
fmt.Fprintln(cmd.Err(), sterr.Status)
|
||||||
fmt.Fprintln(dockerCli.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) {
|
// StatusError should only be used for errors, and all errors should
|
||||||
s.Print(dockerCli.Err())
|
// have a non-zero exit status, so never exit with 0
|
||||||
|
if sterr.StatusCode == 0 {
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
os.Exit(sterr.StatusCode)
|
||||||
if debug.IsEnabled() {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "error: %+v", stack.Formatter(err))
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(dockerCli.Err(), "error: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {}
|
||||||
@@ -6,9 +6,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/docker/buildx/bake"
|
"github.com/docker/buildx/bake"
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/buildx/util/tracing"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@@ -17,20 +20,35 @@ import (
|
|||||||
|
|
||||||
type bakeOptions struct {
|
type bakeOptions struct {
|
||||||
files []string
|
files []string
|
||||||
printOnly bool
|
|
||||||
overrides []string
|
overrides []string
|
||||||
|
printOnly bool
|
||||||
commonOptions
|
commonOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error) {
|
func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error) {
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
|
ctx, end, err := tracing.TraceCurrentCommand(ctx, "bake")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
end(err)
|
||||||
|
}()
|
||||||
|
|
||||||
var url string
|
var url string
|
||||||
|
cmdContext := "cwd://"
|
||||||
|
|
||||||
if len(targets) > 0 {
|
if len(targets) > 0 {
|
||||||
if bake.IsRemoteURL(targets[0]) {
|
if bake.IsRemoteURL(targets[0]) {
|
||||||
url = targets[0]
|
url = targets[0]
|
||||||
targets = targets[1:]
|
targets = targets[1:]
|
||||||
|
if len(targets) > 0 {
|
||||||
|
if bake.IsRemoteURL(targets[0]) {
|
||||||
|
cmdContext = targets[0]
|
||||||
|
targets = targets[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +61,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err 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")
|
||||||
}
|
}
|
||||||
@@ -57,7 +75,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
|||||||
|
|
||||||
ctx2, cancel := context.WithCancel(context.TODO())
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
printer := progress.NewPrinter(ctx2, os.Stderr, in.progress)
|
printer := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, in.progress)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if printer != nil {
|
if printer != nil {
|
||||||
@@ -75,6 +93,7 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
|||||||
|
|
||||||
var files []bake.File
|
var files []bake.File
|
||||||
var inp *bake.Input
|
var inp *bake.Input
|
||||||
|
|
||||||
if url != "" {
|
if url != "" {
|
||||||
files, inp, err = bake.ReadRemoteFiles(ctx, dis, url, in.files, printer)
|
files, inp, err = bake.ReadRemoteFiles(ctx, dis, url, in.files, printer)
|
||||||
} else {
|
} else {
|
||||||
@@ -84,13 +103,36 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := bake.ReadTargets(ctx, files, targets, overrides)
|
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/guides/bake/file-definition.md#built-in-variables
|
||||||
|
"BAKE_CMD_CONTEXT": cmdContext,
|
||||||
|
"BAKE_LOCAL_PLATFORM": platforms.DefaultString(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function can update target context string from the input so call before printOnly check
|
||||||
|
bo, err := bake.TargetsToBuildOpt(tgts, inp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if in.printOnly {
|
if in.printOnly {
|
||||||
dt, err := json.MarshalIndent(map[string]map[string]*bake.Target{"target": m}, "", " ")
|
var defg map[string]*bake.Group
|
||||||
|
if len(grps) == 1 {
|
||||||
|
defg = map[string]*bake.Group{
|
||||||
|
"default": grps[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dt, err := json.MarshalIndent(struct {
|
||||||
|
Group map[string]*bake.Group `json:"group,omitempty"`
|
||||||
|
Target map[string]*bake.Target `json:"target"`
|
||||||
|
}{
|
||||||
|
defg,
|
||||||
|
tgts,
|
||||||
|
}, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -103,12 +145,21 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
bo, err := bake.TargetsToBuildOpt(m, inp)
|
resp, err := build.Build(ctx, dis, bo, dockerAPI(dockerCli), confutil.ConfigDir(dockerCli), printer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return wrapBuildError(err, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = build.Build(ctx, dis, bo, dockerAPI(dockerCli), dockerCli.ConfigFile(), printer)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,10 +186,10 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
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.StringArrayVar(&options.overrides, "set", nil, `Override target value (e.g., "targetpattern.key=value")`)
|
||||||
flags.BoolVar(&options.exportLoad, "load", false, "Shorthand for --set=*.output=type=docker")
|
|
||||||
|
|
||||||
commonBuildFlags(&options.commonOptions, flags)
|
commonBuildFlags(&options.commonOptions, flags)
|
||||||
|
|
||||||
|
|||||||
@@ -1,86 +1,104 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/csv"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/containerd/console"
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
|
"github.com/docker/buildx/monitor"
|
||||||
|
"github.com/docker/buildx/util/buildflags"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
|
"github.com/docker/buildx/util/tracing"
|
||||||
|
"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/docker/cli/cli/config"
|
||||||
|
dockeropts "github.com/docker/cli/opts"
|
||||||
|
"github.com/docker/distribution/reference"
|
||||||
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
|
"github.com/docker/go-units"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
"github.com/moby/buildkit/session/auth/authprovider"
|
"github.com/moby/buildkit/session/auth/authprovider"
|
||||||
|
"github.com/moby/buildkit/solver/errdefs"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
|
"github.com/moby/buildkit/util/grpcerrors"
|
||||||
|
"github.com/morikuni/aec"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultTargetName = "default"
|
||||||
|
|
||||||
type buildOptions struct {
|
type buildOptions struct {
|
||||||
commonOptions
|
|
||||||
contextPath string
|
contextPath string
|
||||||
dockerfileName string
|
dockerfileName string
|
||||||
tags []string
|
printFunc string
|
||||||
labels []string
|
|
||||||
buildArgs []string
|
|
||||||
|
|
||||||
cacheFrom []string
|
allow []string
|
||||||
cacheTo []string
|
buildArgs []string
|
||||||
target string
|
cacheFrom []string
|
||||||
platforms []string
|
cacheTo []string
|
||||||
secrets []string
|
cgroupParent string
|
||||||
ssh []string
|
contexts []string
|
||||||
outputs []string
|
extraHosts []string
|
||||||
imageIDFile string
|
imageIDFile string
|
||||||
extraHosts []string
|
labels []string
|
||||||
networkMode string
|
networkMode string
|
||||||
|
noCacheFilter []string
|
||||||
// unimplemented
|
outputs []string
|
||||||
squash bool
|
platforms []string
|
||||||
quiet bool
|
quiet bool
|
||||||
|
secrets []string
|
||||||
allow []string
|
shmSize dockeropts.MemBytes
|
||||||
|
ssh []string
|
||||||
// hidden
|
tags []string
|
||||||
// untrusted bool
|
target string
|
||||||
// ulimits *opts.UlimitOpt
|
ulimits *dockeropts.UlimitOpt
|
||||||
// memory opts.MemBytes
|
invoke string
|
||||||
// memorySwap opts.MemSwapBytes
|
commonOptions
|
||||||
// shmSize opts.MemBytes
|
|
||||||
// cpuShares int64
|
|
||||||
// cpuPeriod int64
|
|
||||||
// cpuQuota int64
|
|
||||||
// cpuSetCpus string
|
|
||||||
// cpuSetMems string
|
|
||||||
// cgroupParent string
|
|
||||||
// isolation string
|
|
||||||
// compress bool
|
|
||||||
// securityOpt []string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type commonOptions struct {
|
type commonOptions struct {
|
||||||
builder string
|
builder string
|
||||||
noCache *bool
|
metadataFile string
|
||||||
progress string
|
noCache *bool
|
||||||
pull *bool
|
progress string
|
||||||
|
pull *bool
|
||||||
|
|
||||||
|
// golangci-lint#826
|
||||||
|
// nolint:structcheck
|
||||||
exportPush bool
|
exportPush bool
|
||||||
|
// nolint:structcheck
|
||||||
exportLoad bool
|
exportLoad bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBuild(dockerCli command.Cli, in buildOptions) error {
|
func runBuild(dockerCli command.Cli, in buildOptions) (err error) {
|
||||||
if in.squash {
|
|
||||||
return errors.Errorf("squash currently not implemented")
|
|
||||||
}
|
|
||||||
if in.quiet {
|
|
||||||
logrus.Warnf("quiet currently not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
|
ctx, end, err := tracing.TraceCurrentCommand(ctx, "build")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
end(err)
|
||||||
|
}()
|
||||||
|
|
||||||
noCache := false
|
noCache := false
|
||||||
if in.noCache != nil {
|
if in.noCache != nil {
|
||||||
noCache = *in.noCache
|
noCache = *in.noCache
|
||||||
@@ -90,21 +108,46 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
|
|||||||
pull = *in.pull
|
pull = *in.pull
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if noCache && len(in.noCacheFilter) > 0 {
|
||||||
|
return errors.Errorf("--no-cache and --no-cache-filter cannot currently be used together")
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.quiet && in.progress != "auto" && in.progress != "quiet" {
|
||||||
|
return errors.Errorf("progress=%s and quiet cannot be used together", in.progress)
|
||||||
|
} else if in.quiet {
|
||||||
|
in.progress = "quiet"
|
||||||
|
}
|
||||||
|
|
||||||
|
contexts, err := parseContextNames(in.contexts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
printFunc, err := parsePrintFunc(in.printFunc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
opts := build.Options{
|
opts := build.Options{
|
||||||
Inputs: build.Inputs{
|
Inputs: build.Inputs{
|
||||||
ContextPath: in.contextPath,
|
ContextPath: in.contextPath,
|
||||||
DockerfilePath: in.dockerfileName,
|
DockerfilePath: in.dockerfileName,
|
||||||
InStream: os.Stdin,
|
InStream: os.Stdin,
|
||||||
|
NamedContexts: contexts,
|
||||||
},
|
},
|
||||||
Tags: in.tags,
|
BuildArgs: listToMap(in.buildArgs, true),
|
||||||
Labels: listToMap(in.labels, false),
|
ExtraHosts: in.extraHosts,
|
||||||
BuildArgs: listToMap(in.buildArgs, true),
|
ImageIDFile: in.imageIDFile,
|
||||||
Pull: pull,
|
Labels: listToMap(in.labels, false),
|
||||||
NoCache: noCache,
|
NetworkMode: in.networkMode,
|
||||||
Target: in.target,
|
NoCache: noCache,
|
||||||
ImageIDFile: in.imageIDFile,
|
NoCacheFilter: in.noCacheFilter,
|
||||||
ExtraHosts: in.extraHosts,
|
Pull: pull,
|
||||||
NetworkMode: in.networkMode,
|
ShmSize: in.shmSize,
|
||||||
|
Tags: in.tags,
|
||||||
|
Target: in.target,
|
||||||
|
Ulimits: in.ulimits,
|
||||||
|
PrintFunc: printFunc,
|
||||||
}
|
}
|
||||||
|
|
||||||
platforms, err := platformutil.Parse(in.platforms)
|
platforms, err := platformutil.Parse(in.platforms)
|
||||||
@@ -113,21 +156,26 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
|
|||||||
}
|
}
|
||||||
opts.Platforms = platforms
|
opts.Platforms = platforms
|
||||||
|
|
||||||
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(os.Stderr))
|
dockerConfig := config.LoadDefaultConfigFile(os.Stderr)
|
||||||
|
opts.Session = append(opts.Session, authprovider.NewDockerAuthProvider(dockerConfig))
|
||||||
|
|
||||||
secrets, err := build.ParseSecretSpecs(in.secrets)
|
secrets, err := buildflags.ParseSecretSpecs(in.secrets)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
opts.Session = append(opts.Session, secrets)
|
opts.Session = append(opts.Session, secrets)
|
||||||
|
|
||||||
ssh, err := build.ParseSSHSpecs(in.ssh)
|
sshSpecs := in.ssh
|
||||||
|
if len(sshSpecs) == 0 && buildflags.IsGitSSH(in.contextPath) {
|
||||||
|
sshSpecs = []string{"default"}
|
||||||
|
}
|
||||||
|
ssh, err := buildflags.ParseSSHSpecs(sshSpecs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
opts.Session = append(opts.Session, ssh)
|
opts.Session = append(opts.Session, ssh)
|
||||||
|
|
||||||
outputs, err := build.ParseOutputs(in.outputs)
|
outputs, err := buildflags.ParseOutputs(in.outputs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -168,19 +216,19 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
|
|||||||
|
|
||||||
opts.Exports = outputs
|
opts.Exports = outputs
|
||||||
|
|
||||||
cacheImports, err := build.ParseCacheEntry(in.cacheFrom)
|
cacheImports, err := buildflags.ParseCacheEntry(in.cacheFrom)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
opts.CacheFrom = cacheImports
|
opts.CacheFrom = cacheImports
|
||||||
|
|
||||||
cacheExports, err := build.ParseCacheEntry(in.cacheTo)
|
cacheExports, err := buildflags.ParseCacheEntry(in.cacheTo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
opts.CacheTo = cacheExports
|
opts.CacheTo = cacheExports
|
||||||
|
|
||||||
allow, err := build.ParseEntitlements(in.allow)
|
allow, err := buildflags.ParseEntitlements(in.allow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -192,30 +240,190 @@ func runBuild(dockerCli command.Cli, in buildOptions) error {
|
|||||||
contextPathHash = in.contextPath
|
contextPathHash = in.contextPath
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildTargets(ctx, dockerCli, map[string]build.Options{"default": opts}, in.progress, contextPathHash, in.builder)
|
imageID, res, err := buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile, in.invoke != "")
|
||||||
}
|
err = wrapBuildError(err, false)
|
||||||
|
|
||||||
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode, contextPathHash, instance string) error {
|
|
||||||
dis, err := getInstanceOrDefault(ctx, dockerCli, instance, contextPathHash)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if in.invoke != "" {
|
||||||
|
cfg, err := parseInvokeConfig(in.invoke)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg.ResultCtx = res
|
||||||
|
con := console.Current()
|
||||||
|
if err := con.SetRaw(); err != nil {
|
||||||
|
return errors.Errorf("failed to configure terminal: %v", err)
|
||||||
|
}
|
||||||
|
err = monitor.RunMonitor(ctx, cfg, func(ctx context.Context) (*build.ResultContext, error) {
|
||||||
|
_, rr, err := buildTargets(ctx, dockerCli, map[string]build.Options{defaultTargetName: opts}, in.progress, contextPathHash, in.builder, in.metadataFile, true)
|
||||||
|
return rr, err
|
||||||
|
}, io.NopCloser(os.Stdin), nopCloser{os.Stdout}, nopCloser{os.Stderr})
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("failed to run monitor: %v", err)
|
||||||
|
}
|
||||||
|
con.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
if in.quiet {
|
||||||
|
fmt.Println(imageID)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type nopCloser struct {
|
||||||
|
io.WriteCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c nopCloser) Close() error { return nil }
|
||||||
|
|
||||||
|
func buildTargets(ctx context.Context, dockerCli command.Cli, opts map[string]build.Options, progressMode, contextPathHash, instance string, metadataFile string, allowNoOutput bool) (imageID string, res *build.ResultContext, err error) {
|
||||||
|
dis, err := getInstanceOrDefault(ctx, dockerCli, instance, contextPathHash)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
ctx2, cancel := context.WithCancel(context.TODO())
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
printer := progress.NewPrinter(ctx2, os.Stderr, progressMode)
|
|
||||||
|
|
||||||
_, err = build.Build(ctx, dis, opts, dockerAPI(dockerCli), dockerCli.ConfigFile(), printer)
|
printer := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, progressMode)
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
var idx int
|
||||||
|
resp, err := build.BuildWithResultHandler(ctx, dis, opts, dockerAPI(dockerCli), confutil.ConfigDir(dockerCli), printer, func(driverIndex int, gotRes *build.ResultContext) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
if res == nil || driverIndex < idx {
|
||||||
|
idx, res = driverIndex, gotRes
|
||||||
|
}
|
||||||
|
}, allowNoOutput)
|
||||||
err1 := printer.Wait()
|
err1 := printer.Wait()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = err1
|
err = err1
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
if len(metadataFile) > 0 && resp != nil {
|
||||||
|
if err := writeMetadataFile(metadataFile, decodeExporterResponse(resp[defaultTargetName].ExporterResponse)); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
printWarnings(os.Stderr, printer.Warnings(), progressMode)
|
||||||
|
|
||||||
|
for k := range resp {
|
||||||
|
if opts[k].PrintFunc != nil {
|
||||||
|
if err := printResult(opts[k].PrintFunc, resp[k].ExporterResponse); err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp[defaultTargetName].ExporterResponse["containerimage.digest"], res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInvokeConfig(invoke string) (cfg build.ContainerConfig, err error) {
|
||||||
|
csvReader := csv.NewReader(strings.NewReader(invoke))
|
||||||
|
fields, err := csvReader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return cfg, err
|
||||||
|
}
|
||||||
|
cfg.Tty = true
|
||||||
|
if len(fields) == 1 && !strings.Contains(fields[0], "=") {
|
||||||
|
cfg.Args = []string{fields[0]}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
var entrypoint string
|
||||||
|
var args []string
|
||||||
|
for _, field := range fields {
|
||||||
|
parts := strings.SplitN(field, "=", 2)
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return cfg, errors.Errorf("invalid value %s", field)
|
||||||
|
}
|
||||||
|
key := strings.ToLower(parts[0])
|
||||||
|
value := parts[1]
|
||||||
|
switch key {
|
||||||
|
case "args":
|
||||||
|
args = append(args, value) // TODO: support JSON
|
||||||
|
case "entrypoint":
|
||||||
|
entrypoint = value // TODO: support JSON
|
||||||
|
case "env":
|
||||||
|
cfg.Env = append(cfg.Env, value)
|
||||||
|
case "user":
|
||||||
|
cfg.User = value
|
||||||
|
case "cwd":
|
||||||
|
cfg.Cwd = value
|
||||||
|
case "tty":
|
||||||
|
cfg.Tty, err = strconv.ParseBool(value)
|
||||||
|
if err != nil {
|
||||||
|
return cfg, errors.Errorf("failed to parse tty: %v", err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return cfg, errors.Errorf("unknown key %q", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg.Args = args
|
||||||
|
if entrypoint != "" {
|
||||||
|
cfg.Args = append([]string{entrypoint}, cfg.Args...)
|
||||||
|
}
|
||||||
|
if len(cfg.Args) == 0 {
|
||||||
|
cfg.Args = []string{"sh"}
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printWarnings(w io.Writer, warnings []client.VertexWarning, mode string) {
|
||||||
|
if len(warnings) == 0 || mode == progress.PrinterModeQuiet {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "\n ")
|
||||||
|
sb := &bytes.Buffer{}
|
||||||
|
if len(warnings) == 1 {
|
||||||
|
fmt.Fprintf(sb, "1 warning found")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(sb, "%d warnings found", len(warnings))
|
||||||
|
}
|
||||||
|
if logrus.GetLevel() < logrus.DebugLevel {
|
||||||
|
fmt.Fprintf(sb, " (use --debug to expand)")
|
||||||
|
}
|
||||||
|
fmt.Fprintf(sb, ":\n")
|
||||||
|
fmt.Fprint(w, aec.Apply(sb.String(), aec.YellowF))
|
||||||
|
|
||||||
|
for _, warn := range warnings {
|
||||||
|
fmt.Fprintf(w, " - %s\n", warn.Short)
|
||||||
|
if logrus.GetLevel() < logrus.DebugLevel {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, d := range warn.Detail {
|
||||||
|
fmt.Fprintf(w, "%s\n", d)
|
||||||
|
}
|
||||||
|
if warn.URL != "" {
|
||||||
|
fmt.Fprintf(w, "More info: %s\n", warn.URL)
|
||||||
|
}
|
||||||
|
if warn.SourceInfo != nil && warn.Range != nil {
|
||||||
|
src := errdefs.Source{
|
||||||
|
Info: warn.SourceInfo,
|
||||||
|
Ranges: warn.Range,
|
||||||
|
}
|
||||||
|
src.Print(w)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(w, "\n")
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuildOptions() buildOptions {
|
||||||
|
ulimits := make(map[string]*units.Ulimit)
|
||||||
|
return buildOptions{
|
||||||
|
ulimits: dockeropts.NewUlimitOpt(&ulimits),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||||
var options buildOptions
|
options := newBuildOptions()
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "build [OPTIONS] PATH | URL | -",
|
Use: "build [OPTIONS] PATH | URL | -",
|
||||||
@@ -225,94 +433,146 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
options.contextPath = args[0]
|
options.contextPath = args[0]
|
||||||
options.builder = rootOpts.builder
|
options.builder = rootOpts.builder
|
||||||
|
cmd.Flags().VisitAll(checkWarnedFlags)
|
||||||
return runBuild(dockerCli, options)
|
return runBuild(dockerCli, options)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var platformsDefault []string
|
||||||
|
if v := os.Getenv("DOCKER_DEFAULT_PLATFORM"); v != "" {
|
||||||
|
platformsDefault = []string{v}
|
||||||
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
|
|
||||||
flags.BoolVar(&options.exportPush, "push", false, "Shorthand for --output=type=registry")
|
flags.StringSliceVar(&options.extraHosts, "add-host", []string{}, `Add a custom host-to-IP mapping (format: "host:ip")`)
|
||||||
flags.BoolVar(&options.exportLoad, "load", false, "Shorthand for --output=type=docker")
|
flags.SetAnnotation("add-host", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host"})
|
||||||
|
|
||||||
|
flags.StringSliceVar(&options.allow, "allow", []string{}, `Allow extra privileged entitlement (e.g., "network.host", "security.insecure")`)
|
||||||
|
|
||||||
flags.StringArrayVarP(&options.tags, "tag", "t", []string{}, "Name and optionally a tag in the 'name:tag' format")
|
|
||||||
flags.StringArrayVar(&options.buildArgs, "build-arg", []string{}, "Set build-time variables")
|
flags.StringArrayVar(&options.buildArgs, "build-arg", []string{}, "Set build-time variables")
|
||||||
flags.StringVarP(&options.dockerfileName, "file", "f", "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
|
|
||||||
|
flags.StringArrayVar(&options.cacheFrom, "cache-from", []string{}, `External cache sources (e.g., "user/app:cache", "type=local,src=path/to/dir")`)
|
||||||
|
|
||||||
|
flags.StringArrayVar(&options.cacheTo, "cache-to", []string{}, `Cache export destinations (e.g., "user/app:cache", "type=local,dest=path/to/dir")`)
|
||||||
|
|
||||||
|
flags.StringVar(&options.cgroupParent, "cgroup-parent", "", "Optional parent cgroup for the container")
|
||||||
|
flags.SetAnnotation("cgroup-parent", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#use-a-custom-parent-cgroup---cgroup-parent"})
|
||||||
|
|
||||||
|
flags.StringArrayVar(&options.contexts, "build-context", []string{}, "Additional build contexts (e.g., name=path)")
|
||||||
|
|
||||||
|
flags.StringVarP(&options.dockerfileName, "file", "f", "", `Name of the Dockerfile (default: "PATH/Dockerfile")`)
|
||||||
|
flags.SetAnnotation("file", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f"})
|
||||||
|
|
||||||
|
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
||||||
|
|
||||||
flags.StringArrayVar(&options.labels, "label", []string{}, "Set metadata for an image")
|
flags.StringArrayVar(&options.labels, "label", []string{}, "Set metadata for an image")
|
||||||
|
|
||||||
flags.StringArrayVar(&options.cacheFrom, "cache-from", []string{}, "External cache sources (eg. user/app:cache, type=local,src=path/to/dir)")
|
flags.BoolVar(&options.exportLoad, "load", false, `Shorthand for "--output=type=docker"`)
|
||||||
flags.StringArrayVar(&options.cacheTo, "cache-to", []string{}, "Cache export destinations (eg. user/app:cache, type=local,dest=path/to/dir)")
|
|
||||||
|
|
||||||
flags.StringVar(&options.target, "target", "", "Set the target build stage to build.")
|
flags.StringVar(&options.networkMode, "network", "default", `Set the networking mode for the "RUN" instructions during build`)
|
||||||
|
|
||||||
flags.StringSliceVar(&options.allow, "allow", []string{}, "Allow extra privileged entitlement, e.g. network.host, security.insecure")
|
flags.StringArrayVar(&options.noCacheFilter, "no-cache-filter", []string{}, "Do not cache specified stages")
|
||||||
|
|
||||||
|
flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, `Output destination (format: "type=local,dest=path")`)
|
||||||
|
|
||||||
|
flags.StringArrayVar(&options.platforms, "platform", platformsDefault, "Set target platform for build")
|
||||||
|
|
||||||
|
if isExperimental() {
|
||||||
|
flags.StringVar(&options.printFunc, "print", "", "Print result of information request (e.g., outline, targets) [experimental]")
|
||||||
|
}
|
||||||
|
|
||||||
|
flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--output=type=registry"`)
|
||||||
|
|
||||||
// not implemented
|
|
||||||
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
|
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Suppress the build output and print image ID on success")
|
||||||
flags.StringVar(&options.networkMode, "network", "default", "Set the networking mode for the RUN instructions during build")
|
|
||||||
flags.StringSliceVar(&options.extraHosts, "add-host", []string{}, "Add a custom host-to-IP mapping (host:ip)")
|
flags.StringArrayVar(&options.secrets, "secret", []string{}, `Secret to expose to the build (format: "id=mysecret[,src=/local/secret]")`)
|
||||||
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
|
||||||
flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer")
|
flags.Var(&options.shmSize, "shm-size", `Size of "/dev/shm"`)
|
||||||
flags.MarkHidden("quiet")
|
|
||||||
flags.MarkHidden("squash")
|
flags.StringArrayVar(&options.ssh, "ssh", []string{}, `SSH agent socket or keys to expose to the build (format: "default|<id>[=<socket>|<key>[,<key>]]")`)
|
||||||
|
|
||||||
|
flags.StringArrayVarP(&options.tags, "tag", "t", []string{}, `Name and optionally a tag (format: "name:tag")`)
|
||||||
|
flags.SetAnnotation("tag", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t"})
|
||||||
|
|
||||||
|
flags.StringVar(&options.target, "target", "", "Set the target build stage to build")
|
||||||
|
flags.SetAnnotation("target", annotation.ExternalURL, []string{"https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target"})
|
||||||
|
|
||||||
|
flags.Var(options.ulimits, "ulimit", "Ulimit options")
|
||||||
|
|
||||||
|
if isExperimental() {
|
||||||
|
flags.StringVar(&options.invoke, "invoke", "", "Invoke a command after the build [experimental]")
|
||||||
|
}
|
||||||
|
|
||||||
// hidden flags
|
// hidden flags
|
||||||
var ignore string
|
var ignore string
|
||||||
var ignoreSlice []string
|
var ignoreSlice []string
|
||||||
var ignoreBool bool
|
var ignoreBool bool
|
||||||
var ignoreInt int64
|
var ignoreInt int64
|
||||||
flags.StringVar(&ignore, "ulimit", "", "Ulimit options")
|
|
||||||
flags.MarkHidden("ulimit")
|
|
||||||
flags.StringSliceVar(&ignoreSlice, "security-opt", []string{}, "Security options")
|
|
||||||
flags.MarkHidden("security-opt")
|
|
||||||
flags.BoolVar(&ignoreBool, "compress", false, "Compress the build context using gzip")
|
flags.BoolVar(&ignoreBool, "compress", false, "Compress the build context using gzip")
|
||||||
flags.MarkHidden("compress")
|
flags.MarkHidden("compress")
|
||||||
flags.StringVarP(&ignore, "memory", "m", "", "Memory limit")
|
|
||||||
flags.MarkHidden("memory")
|
|
||||||
flags.StringVar(&ignore, "memory-swap", "", "Swap limit equal to memory plus swap: '-1' to enable unlimited swap")
|
|
||||||
flags.MarkHidden("memory-swap")
|
|
||||||
flags.StringVar(&ignore, "shm-size", "", "Size of /dev/shm")
|
|
||||||
flags.MarkHidden("shm-size")
|
|
||||||
flags.Int64VarP(&ignoreInt, "cpu-shares", "c", 0, "CPU shares (relative weight)")
|
|
||||||
flags.MarkHidden("cpu-shares")
|
|
||||||
flags.Int64Var(&ignoreInt, "cpu-period", 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
|
||||||
flags.MarkHidden("cpu-period")
|
|
||||||
flags.Int64Var(&ignoreInt, "cpu-quota", 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
|
||||||
flags.MarkHidden("cpu-quota")
|
|
||||||
flags.StringVar(&ignore, "cpuset-cpus", "", "CPUs in which to allow execution (0-3, 0,1)")
|
|
||||||
flags.MarkHidden("cpuset-cpus")
|
|
||||||
flags.StringVar(&ignore, "cpuset-mems", "", "MEMs in which to allow execution (0-3, 0,1)")
|
|
||||||
flags.MarkHidden("cpuset-mems")
|
|
||||||
flags.StringVar(&ignore, "cgroup-parent", "", "Optional parent cgroup for the container")
|
|
||||||
flags.MarkHidden("cgroup-parent")
|
|
||||||
flags.StringVar(&ignore, "isolation", "", "Container isolation technology")
|
flags.StringVar(&ignore, "isolation", "", "Container isolation technology")
|
||||||
flags.MarkHidden("isolation")
|
flags.MarkHidden("isolation")
|
||||||
|
flags.SetAnnotation("isolation", "flag-warn", []string{"isolation flag is deprecated with BuildKit."})
|
||||||
|
|
||||||
|
flags.StringSliceVar(&ignoreSlice, "security-opt", []string{}, "Security options")
|
||||||
|
flags.MarkHidden("security-opt")
|
||||||
|
flags.SetAnnotation("security-opt", "flag-warn", []string{`security-opt flag is deprecated. "RUN --security=insecure" should be used with BuildKit.`})
|
||||||
|
|
||||||
|
flags.BoolVar(&ignoreBool, "squash", false, "Squash newly built layers into a single new layer")
|
||||||
|
flags.MarkHidden("squash")
|
||||||
|
flags.SetAnnotation("squash", "flag-warn", []string{"experimental flag squash is removed with BuildKit. You should squash inside build using a multi-stage Dockerfile for efficiency."})
|
||||||
|
|
||||||
|
flags.StringVarP(&ignore, "memory", "m", "", "Memory limit")
|
||||||
|
flags.MarkHidden("memory")
|
||||||
|
|
||||||
|
flags.StringVar(&ignore, "memory-swap", "", `Swap limit equal to memory plus swap: "-1" to enable unlimited swap`)
|
||||||
|
flags.MarkHidden("memory-swap")
|
||||||
|
|
||||||
|
flags.Int64VarP(&ignoreInt, "cpu-shares", "c", 0, "CPU shares (relative weight)")
|
||||||
|
flags.MarkHidden("cpu-shares")
|
||||||
|
|
||||||
|
flags.Int64Var(&ignoreInt, "cpu-period", 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
||||||
|
flags.MarkHidden("cpu-period")
|
||||||
|
|
||||||
|
flags.Int64Var(&ignoreInt, "cpu-quota", 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
||||||
|
flags.MarkHidden("cpu-quota")
|
||||||
|
|
||||||
|
flags.StringVar(&ignore, "cpuset-cpus", "", `CPUs in which to allow execution ("0-3", "0,1")`)
|
||||||
|
flags.MarkHidden("cpuset-cpus")
|
||||||
|
|
||||||
|
flags.StringVar(&ignore, "cpuset-mems", "", `MEMs in which to allow execution ("0-3", "0,1")`)
|
||||||
|
flags.MarkHidden("cpuset-mems")
|
||||||
|
|
||||||
flags.BoolVar(&ignoreBool, "rm", true, "Remove intermediate containers after a successful build")
|
flags.BoolVar(&ignoreBool, "rm", true, "Remove intermediate containers after a successful build")
|
||||||
flags.MarkHidden("rm")
|
flags.MarkHidden("rm")
|
||||||
|
|
||||||
flags.BoolVar(&ignoreBool, "force-rm", false, "Always remove intermediate containers")
|
flags.BoolVar(&ignoreBool, "force-rm", false, "Always remove intermediate containers")
|
||||||
flags.MarkHidden("force-rm")
|
flags.MarkHidden("force-rm")
|
||||||
|
|
||||||
platformsDefault := []string{}
|
|
||||||
if v := os.Getenv("DOCKER_DEFAULT_PLATFORM"); v != "" {
|
|
||||||
platformsDefault = []string{v}
|
|
||||||
}
|
|
||||||
flags.StringArrayVar(&options.platforms, "platform", platformsDefault, "Set target platform for build")
|
|
||||||
|
|
||||||
flags.StringArrayVar(&options.secrets, "secret", []string{}, "Secret file to expose to the build: id=mysecret,src=/local/secret")
|
|
||||||
|
|
||||||
flags.StringArrayVar(&options.ssh, "ssh", []string{}, "SSH agent socket or keys to expose to the build (format: default|<id>[=<socket>|<key>[,<key>]])")
|
|
||||||
|
|
||||||
flags.StringArrayVarP(&options.outputs, "output", "o", []string{}, "Output destination (format: type=local,dest=path)")
|
|
||||||
|
|
||||||
commonBuildFlags(&options.commonOptions, flags)
|
commonBuildFlags(&options.commonOptions, flags)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
func commonBuildFlags(options *commonOptions, flags *pflag.FlagSet) {
|
func commonBuildFlags(options *commonOptions, flags *pflag.FlagSet) {
|
||||||
options.noCache = flags.Bool("no-cache", false, "Do not use cache when building the image")
|
options.noCache = flags.Bool("no-cache", false, "Do not use cache when building the image")
|
||||||
flags.StringVar(&options.progress, "progress", "auto", "Set type of progress output (auto, plain, tty). Use plain to show container output")
|
flags.StringVar(&options.progress, "progress", "auto", `Set type of progress output ("auto", "plain", "tty"). Use plain to show container output`)
|
||||||
options.pull = flags.Bool("pull", false, "Always attempt to pull a newer version of the image")
|
options.pull = flags.Bool("pull", false, "Always attempt to pull all referenced images")
|
||||||
|
flags.StringVar(&options.metadataFile, "metadata-file", "", "Write build result metadata to the file")
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkWarnedFlags(f *pflag.Flag) {
|
||||||
|
if !f.Changed {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for t, m := range f.Annotations {
|
||||||
|
switch t {
|
||||||
|
case "flag-warn":
|
||||||
|
logrus.Warn(m[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func listToMap(values []string, defaultEnv bool) map[string]string {
|
func listToMap(values []string, defaultEnv bool) map[string]string {
|
||||||
@@ -334,3 +594,116 @@ func listToMap(values []string, defaultEnv bool) map[string]string {
|
|||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseContextNames(values []string) (map[string]build.NamedContext, error) {
|
||||||
|
if len(values) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
result := make(map[string]build.NamedContext, len(values))
|
||||||
|
for _, value := range values {
|
||||||
|
kv := strings.SplitN(value, "=", 2)
|
||||||
|
if len(kv) != 2 {
|
||||||
|
return nil, errors.Errorf("invalid context value: %s, expected key=value", value)
|
||||||
|
}
|
||||||
|
named, err := reference.ParseNormalizedNamed(kv[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "invalid context name %s", kv[0])
|
||||||
|
}
|
||||||
|
name := strings.TrimSuffix(reference.FamiliarString(named), ":latest")
|
||||||
|
result[name] = build.NamedContext{Path: kv[1]}
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parsePrintFunc(str string) (*build.PrintFunc, error) {
|
||||||
|
if str == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
csvReader := csv.NewReader(strings.NewReader(str))
|
||||||
|
fields, err := csvReader.Read()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
f := &build.PrintFunc{}
|
||||||
|
for _, field := range fields {
|
||||||
|
parts := strings.SplitN(field, "=", 2)
|
||||||
|
if len(parts) == 2 {
|
||||||
|
if parts[0] == "format" {
|
||||||
|
f.Format = parts[1]
|
||||||
|
} else {
|
||||||
|
return nil, errors.Errorf("invalid print field: %s", field)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if f.Name != "" {
|
||||||
|
return nil, errors.Errorf("invalid print value: %s", str)
|
||||||
|
}
|
||||||
|
f.Name = field
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeMetadataFile(filename string, dt interface{}) error {
|
||||||
|
b, err := json.MarshalIndent(dt, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutils.AtomicWriteFile(filename, b, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeExporterResponse(exporterResponse map[string]string) map[string]interface{} {
|
||||||
|
out := make(map[string]interface{})
|
||||||
|
for k, v := range exporterResponse {
|
||||||
|
dt, err := base64.StdEncoding.DecodeString(v)
|
||||||
|
if err != nil {
|
||||||
|
out[k] = v
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var raw map[string]interface{}
|
||||||
|
if err = json.Unmarshal(dt, &raw); err != nil || len(raw) == 0 {
|
||||||
|
out[k] = v
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[k] = json.RawMessage(dt)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
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 isExperimental() bool {
|
||||||
|
if v, ok := os.LookupEnv("BUILDX_EXPERIMENTAL"); ok {
|
||||||
|
vv, _ := strconv.ParseBool(v)
|
||||||
|
return vv
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
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/driver"
|
"github.com/docker/buildx/driver"
|
||||||
"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/confutil"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/google/shlex"
|
"github.com/google/shlex"
|
||||||
@@ -28,6 +35,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,23 +61,7 @@ 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
|
||||||
}
|
}
|
||||||
@@ -83,6 +75,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 +95,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)
|
||||||
|
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,37 +160,72 @@ 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
|
||||||
}
|
}
|
||||||
} 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])
|
||||||
|
}
|
||||||
|
// naming endpoint to make --append works
|
||||||
|
ep = (&url.URL{
|
||||||
|
Scheme: driverName,
|
||||||
|
Path: "/" + in.name,
|
||||||
|
RawQuery: (&url.Values{
|
||||||
|
"deployment": {in.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 = storeutil.GetCurrentEndpoint(dockerCli)
|
||||||
ep, err = getCurrentEndpoint(dockerCli)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
setEp = false
|
||||||
|
|
||||||
if in.driver == "kubernetes" {
|
|
||||||
// naming endpoint to make --append works
|
|
||||||
ep = fmt.Sprintf("%s://%s?deployment=%s", in.driver, in.name, in.nodeName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,8 +234,32 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ngi := &nginfo{ng: ng}
|
||||||
|
|
||||||
|
timeoutCtx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
if err = loadNodeGroupData(timeoutCtx, dockerCli, ngi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, info := range ngi.drivers {
|
||||||
|
if err := info.di.Err; err != nil {
|
||||||
|
err := errors.Errorf("failed to initialize builder %s (%s): %s", ng.Name, info.di.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 := storeutil.GetCurrentEndpoint(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -171,6 +268,12 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if in.bootstrap {
|
||||||
|
if _, err = boot(ctx, ngi); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Printf("%s\n", ng.Name)
|
fmt.Printf("%s\n", ng.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -178,9 +281,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{
|
||||||
@@ -195,23 +301,28 @@ func createCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
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))
|
||||||
|
|||||||
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")
|
||||||
|
}
|
||||||
@@ -4,16 +4,18 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -125,20 +127,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)
|
||||||
@@ -159,11 +161,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) {
|
||||||
@@ -185,14 +191,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,12 +1,16 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/store"
|
||||||
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"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/docker/distribution/reference"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/moby/buildkit/util/appcontext"
|
||||||
@@ -18,10 +22,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type createOptions struct {
|
type createOptions struct {
|
||||||
|
builder string
|
||||||
files []string
|
files []string
|
||||||
tags []string
|
tags []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 +41,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,18 +81,21 @@ 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 && s.Desc.MediaType == "" && s.Desc.Digest != "" {
|
||||||
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
|
||||||
}
|
}
|
||||||
@@ -101,9 +110,32 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
|
|
||||||
ctx := appcontext.Context()
|
ctx := appcontext.Context()
|
||||||
|
|
||||||
r := imagetools.New(imagetools.Opt{
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
Auth: dockerCli.ConfigFile(),
|
if err != nil {
|
||||||
})
|
return err
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
|
||||||
|
var ng *store.NodeGroup
|
||||||
|
|
||||||
|
if in.builder != "" {
|
||||||
|
ng, err = storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ng, err = storeutil.GetCurrentInstance(txn, dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imageopt, err := storeutil.GetImageConfig(dockerCli, ng)
|
||||||
|
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 +149,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 +167,7 @@ func runCreate(dockerCli command.Cli, in createOptions, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
descs := make([]ocispec.Descriptor, len(srcs))
|
dt, desc, err := r.Combine(ctx, srcs)
|
||||||
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 +178,55 @@ 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(),
|
|
||||||
})
|
ctx2, cancel := context.WithCancel(context.TODO())
|
||||||
|
defer cancel()
|
||||||
|
printer := progress.NewPrinter(ctx2, os.Stderr, os.Stderr, in.progress)
|
||||||
|
|
||||||
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
|
pw := progress.WithPrefix(printer, "internal", true)
|
||||||
|
|
||||||
for _, t := range tags {
|
for _, t := range tags {
|
||||||
if err := r.Push(ctx, t, desc, dt); err != nil {
|
t := t
|
||||||
return err
|
eg.Go(func() error {
|
||||||
}
|
return progress.Wrap(fmt.Sprintf("pushing %s", t.String()), pw.Write, func(sub progress.SubLogger) error {
|
||||||
fmt.Println(t.String())
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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 +245,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 +260,54 @@ 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)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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,82 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"github.com/docker/buildx/store"
|
||||||
"os"
|
"github.com/docker/buildx/store/storeutil"
|
||||||
|
|
||||||
"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)
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer release()
|
||||||
|
|
||||||
|
var ng *store.NodeGroup
|
||||||
|
|
||||||
|
if in.builder != "" {
|
||||||
|
ng, err = storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ng, err = storeutil.GetCurrentInstance(txn, dockerCli)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
imageopt, err := storeutil.GetImageConfig(dockerCli, ng)
|
||||||
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])
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,15 +5,19 @@ import (
|
|||||||
"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",
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.AddCommand(
|
cmd.AddCommand(
|
||||||
inspectCmd(dockerCli),
|
createCmd(dockerCli, opts),
|
||||||
createCmd(dockerCli),
|
inspectCmd(dockerCli, opts),
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|||||||
@@ -8,17 +8,13 @@ import (
|
|||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
|
||||||
"github.com/docker/buildx/driver"
|
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
"github.com/docker/buildx/util/progress"
|
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/moby/buildkit/util/appcontext"
|
"github.com/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,23 +22,10 @@ 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)
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -51,12 +34,12 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
|||||||
var ng *store.NodeGroup
|
var ng *store.NodeGroup
|
||||||
|
|
||||||
if in.builder != "" {
|
if in.builder != "" {
|
||||||
ng, err = getNodeGroup(txn, dockerCli, in.builder)
|
ng, err = storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ng, err = getCurrentInstance(txn, dockerCli)
|
ng, err = storeutil.GetCurrentInstance(txn, dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -82,7 +65,7 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
|||||||
var bootNgi *nginfo
|
var bootNgi *nginfo
|
||||||
if in.bootstrap {
|
if in.bootstrap {
|
||||||
var ok bool
|
var ok bool
|
||||||
ok, err = boot(ctx, ngi, dockerCli)
|
ok, err = boot(ctx, ngi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -96,6 +79,7 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
|||||||
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", ngi.ng.Name)
|
||||||
fmt.Fprintf(w, "Driver:\t%s\n", ngi.ng.Driver)
|
fmt.Fprintf(w, "Driver:\t%s\n", ngi.ng.Driver)
|
||||||
|
|
||||||
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 ngi.err != nil {
|
||||||
@@ -111,6 +95,15 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
|||||||
}
|
}
|
||||||
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)
|
||||||
|
|
||||||
|
var driverOpts []string
|
||||||
|
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 := ngi.drivers[i].di.Err; err != nil {
|
if err := ngi.drivers[i].di.Err; err != nil {
|
||||||
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
fmt.Fprintf(w, "Error:\t%s\n", err.Error())
|
||||||
} else if err := ngi.drivers[i].err; err != nil {
|
} else if err := ngi.drivers[i].err; err != nil {
|
||||||
@@ -122,6 +115,9 @@ func runInspect(dockerCli command.Cli, in inspectOptions) error {
|
|||||||
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, " "))
|
||||||
}
|
}
|
||||||
|
if ngi.drivers[i].version != "" {
|
||||||
|
fmt.Fprintf(w, "Buildkit:\t%s\n", ngi.drivers[i].version)
|
||||||
|
}
|
||||||
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.FormatInGroups(n.Platforms, ngi.drivers[i].platforms), ", "))
|
fmt.Fprintf(w, "Platforms:\t%s\n", strings.Join(platformutil.FormatInGroups(n.Platforms, ngi.drivers[i].platforms), ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,49 +145,7 @@ func inspectCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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, dockerCli command.Cli) (bool, error) {
|
|
||||||
toBoot := make([]int, 0, len(ngi.drivers))
|
|
||||||
for i, d := range ngi.drivers {
|
|
||||||
if d.err != nil || d.di.Err != nil || d.di.Driver == nil || d.info == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if d.info.Status != driver.Running {
|
|
||||||
toBoot = append(toBoot, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(toBoot) == 0 {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
printer := progress.NewPrinter(context.TODO(), os.Stderr, "auto")
|
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
|
||||||
for _, idx := range toBoot {
|
|
||||||
func(idx int) {
|
|
||||||
eg.Go(func() error {
|
|
||||||
pw := progress.WithPrefix(printer, 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
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}(idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := eg.Wait()
|
|
||||||
err1 := printer.Wait()
|
|
||||||
if err == nil {
|
|
||||||
err = err1
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
"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"
|
||||||
@@ -48,5 +49,8 @@ func installCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
Hidden: true,
|
Hidden: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide builder persistent flag for this command
|
||||||
|
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/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,7 +26,7 @@ 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
|
||||||
}
|
}
|
||||||
@@ -43,23 +45,30 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
builders[i] = &nginfo{ng: ng}
|
builders[i] = &nginfo{ng: ng}
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := dockerCli.ContextStore().List()
|
contexts, err := dockerCli.ContextStore().List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ctxbuilders := make([]*nginfo, len(list))
|
sort.Slice(contexts, func(i, j int) bool {
|
||||||
for i, l := range list {
|
return contexts[i].Name < contexts[j].Name
|
||||||
ctxbuilders[i] = &nginfo{ng: &store.NodeGroup{
|
})
|
||||||
Name: l.Name,
|
for _, c := range contexts {
|
||||||
|
ngi := &nginfo{ng: &store.NodeGroup{
|
||||||
|
Name: c.Name,
|
||||||
Nodes: []store.Node{{
|
Nodes: []store.Node{{
|
||||||
Name: l.Name,
|
Name: c.Name,
|
||||||
Endpoint: l.Name,
|
Endpoint: c.Name,
|
||||||
}},
|
}},
|
||||||
}}
|
}}
|
||||||
|
// 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 hasNodeGroup(builders, ngi) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
builders = append(builders, ngi)
|
||||||
}
|
}
|
||||||
|
|
||||||
builders = append(builders, ctxbuilders...)
|
|
||||||
|
|
||||||
eg, _ := errgroup.WithContext(ctx)
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
@@ -79,7 +88,7 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
currentName := "default"
|
currentName := "default"
|
||||||
current, err := getCurrentInstance(txn, dockerCli)
|
current, err := storeutil.GetCurrentInstance(txn, dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -90,49 +99,72 @@ func runLs(dockerCli command.Cli, in lsOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0)
|
w := tabwriter.NewWriter(dockerCli.Out(), 0, 0, 1, ' ', 0)
|
||||||
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tPLATFORMS\n")
|
fmt.Fprintf(w, "NAME/NODE\tDRIVER/ENDPOINT\tSTATUS\tBUILDKIT\tPLATFORMS\n")
|
||||||
|
|
||||||
currentSet := false
|
currentSet := false
|
||||||
|
printErr := false
|
||||||
for _, b := range builders {
|
for _, b := range builders {
|
||||||
if !currentSet && b.ng.Name == currentName {
|
if !currentSet && b.ng.Name == currentName {
|
||||||
b.ng.Name += " *"
|
b.ng.Name += " *"
|
||||||
currentSet = true
|
currentSet = true
|
||||||
}
|
}
|
||||||
printngi(w, b)
|
if ok := printngi(w, b); !ok {
|
||||||
|
printErr = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Flush()
|
w.Flush()
|
||||||
|
|
||||||
|
if printErr {
|
||||||
|
_, _ = fmt.Fprintf(dockerCli.Err(), "\n")
|
||||||
|
for _, b := range builders {
|
||||||
|
if b.err != nil {
|
||||||
|
_, _ = fmt.Fprintf(dockerCli.Err(), "Cannot load builder %s: %s\n", b.ng.Name, strings.TrimSpace(b.err.Error()))
|
||||||
|
} else {
|
||||||
|
for idx, n := range b.ng.Nodes {
|
||||||
|
d := b.drivers[idx]
|
||||||
|
var nodeErr string
|
||||||
|
if d.err != nil {
|
||||||
|
nodeErr = d.err.Error()
|
||||||
|
} else if d.di.Err != nil {
|
||||||
|
nodeErr = d.di.Err.Error()
|
||||||
|
}
|
||||||
|
if nodeErr != "" {
|
||||||
|
_, _ = fmt.Fprintf(dockerCli.Err(), "Failed to get status for %s (%s): %s\n", b.ng.Name, n.Name, strings.TrimSpace(nodeErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printngi(w io.Writer, ngi *nginfo) {
|
func printngi(w io.Writer, ngi *nginfo) (ok bool) {
|
||||||
|
ok = true
|
||||||
var err string
|
var err string
|
||||||
if ngi.err != nil {
|
if ngi.err != nil {
|
||||||
err = ngi.err.Error()
|
ok = false
|
||||||
|
err = "error"
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "%s\t%s\t%s\t\n", ngi.ng.Name, ngi.ng.Driver, err)
|
fmt.Fprintf(w, "%s\t%s\t%s\t\t\n", ngi.ng.Name, ngi.ng.Driver, err)
|
||||||
if ngi.err == nil {
|
if ngi.err == nil {
|
||||||
for idx, n := range ngi.ng.Nodes {
|
for idx, n := range ngi.ng.Nodes {
|
||||||
d := ngi.drivers[idx]
|
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
|
var status string
|
||||||
if d.info != nil {
|
if d.info != nil {
|
||||||
status = d.info.Status.String()
|
status = d.info.Status.String()
|
||||||
}
|
}
|
||||||
if err != "" {
|
if d.err != nil || d.di.Err != nil {
|
||||||
fmt.Fprintf(w, " %s\t%s\t%s\n", n.Name, n.Endpoint, err)
|
ok = false
|
||||||
|
fmt.Fprintf(w, " %s\t%s\t%s\t\t\n", n.Name, n.Endpoint, "error")
|
||||||
} 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), ", "))
|
fmt.Fprintf(w, " %s\t%s\t%s\t%s\t%s\n", n.Name, n.Endpoint, status, d.version, strings.Join(platformutil.FormatInGroups(n.Platforms, d.platforms), ", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func lsCmd(dockerCli command.Cli) *cobra.Command {
|
func lsCmd(dockerCli command.Cli) *cobra.Command {
|
||||||
@@ -147,5 +179,8 @@ func lsCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide builder persistent flag for this command
|
||||||
|
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
48
commands/print.go
Normal file
48
commands/print.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/build"
|
||||||
|
"github.com/docker/docker/api/types/versions"
|
||||||
|
"github.com/moby/buildkit/frontend/subrequests"
|
||||||
|
"github.com/moby/buildkit/frontend/subrequests/outline"
|
||||||
|
"github.com/moby/buildkit/frontend/subrequests/targets"
|
||||||
|
)
|
||||||
|
|
||||||
|
func printResult(f *build.PrintFunc, res map[string]string) error {
|
||||||
|
switch f.Name {
|
||||||
|
case "outline":
|
||||||
|
return printValue(outline.PrintOutline, outline.SubrequestsOutlineDefinition.Version, f.Format, res)
|
||||||
|
case "targets":
|
||||||
|
return printValue(targets.PrintTargets, targets.SubrequestsTargetsDefinition.Version, f.Format, res)
|
||||||
|
case "subrequests.describe":
|
||||||
|
return printValue(subrequests.PrintDescribe, subrequests.SubrequestsDescribeDefinition.Version, f.Format, res)
|
||||||
|
default:
|
||||||
|
if dt, ok := res["result.txt"]; ok {
|
||||||
|
fmt.Print(dt)
|
||||||
|
} else {
|
||||||
|
log.Printf("%s %+v", f, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type printFunc func([]byte, io.Writer) error
|
||||||
|
|
||||||
|
func printValue(printer printFunc, version string, format string, res map[string]string) error {
|
||||||
|
if format == "json" {
|
||||||
|
fmt.Fprintln(os.Stdout, res["result.json"])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if res["version"] != "" && versions.LessThan(version, res["version"]) && res["result.txt"] != "" {
|
||||||
|
// structure is too new and we don't know how to print it
|
||||||
|
fmt.Fprint(os.Stdout, res["result.txt"])
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return printer([]byte(res["result.json"]), os.Stdout)
|
||||||
|
}
|
||||||
@@ -12,11 +12,11 @@ import (
|
|||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -119,7 +119,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,7 +129,7 @@ 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
|
||||||
@@ -138,8 +138,8 @@ func pruneCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVarP(&options.all, "all", "a", false, "Remove all unused images, not just dangling ones")
|
flags.BoolVarP(&options.all, "all", "a", false, "Include internal/frontend images")
|
||||||
flags.Var(&options.filter, "filter", "Provide filter values (e.g. 'until=24h')")
|
flags.Var(&options.filter, "filter", `Provide filter values (e.g., "until=24h")`)
|
||||||
flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")
|
flags.Var(&options.keepStorage, "keep-storage", "Amount of disk space to keep for cache")
|
||||||
flags.BoolVar(&options.verbose, "verbose", false, "Provide a more verbose output")
|
flags.BoolVar(&options.verbose, "verbose", false, "Provide a more verbose output")
|
||||||
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
flags.BoolVarP(&options.force, "force", "f", false, "Do not prompt for confirmation")
|
||||||
@@ -155,9 +155,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...)
|
||||||
|
|
||||||
@@ -168,23 +168,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")
|
||||||
@@ -192,6 +196,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
|
||||||
}
|
}
|
||||||
|
|||||||
130
commands/rm.go
130
commands/rm.go
@@ -2,51 +2,83 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"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.allInactive {
|
||||||
|
return rmAllInactive(ctx, txn, dockerCli, in)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ng *store.NodeGroup
|
||||||
if in.builder != "" {
|
if in.builder != "" {
|
||||||
ng, err := getNodeGroup(txn, dockerCli, in.builder)
|
ng, err = storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err1 := stop(ctx, dockerCli, ng, true)
|
} else {
|
||||||
if err := txn.Remove(ng.Name); err != nil {
|
ng, err = storeutil.GetCurrentInstance(txn, dockerCli)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return err1
|
}
|
||||||
|
if ng == nil {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ng, err := getCurrentInstance(txn, dockerCli)
|
ctxbuilders, err := dockerCli.ContextStore().List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if ng != nil {
|
for _, cb := range ctxbuilders {
|
||||||
err1 := stop(ctx, dockerCli, ng, true)
|
if ng.Driver == "docker" && len(ng.Nodes) == 1 && ng.Nodes[0].Endpoint == cb.Name {
|
||||||
if err := txn.Remove(ng.Name); err != nil {
|
return errors.Errorf("context builder cannot be removed, run `docker context rm %s` to remove this context", cb.Name)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err1 := rm(ctx, dockerCli, in, ng)
|
||||||
|
if err := txn.Remove(ng.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err1 != nil {
|
||||||
return err1
|
return err1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", ng.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,30 +92,41 @@ 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)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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, dockerCli command.Cli, in rmOptions, ng *store.NodeGroup) error {
|
||||||
dis, err := driversForNodeGroup(ctx, dockerCli, ng, "")
|
dis, err := driversForNodeGroup(ctx, dockerCli, ng, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, di := range dis {
|
for _, di := range dis {
|
||||||
if di.Driver != nil {
|
if di.Driver == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Do not stop the buildkitd daemon when --keep-daemon is provided
|
||||||
|
if !in.keepDaemon {
|
||||||
if err := di.Driver.Stop(ctx, true); err != nil {
|
if err := di.Driver.Stop(ctx, true); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if rm {
|
}
|
||||||
if err := di.Driver.Rm(ctx, true); err != nil {
|
if err := di.Driver.Rm(ctx, true, !in.keepState, !in.keepDaemon); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if di.Err != nil {
|
if di.Err != nil {
|
||||||
err = di.Err
|
err = di.Err
|
||||||
@@ -92,25 +135,42 @@ func stop(ctx context.Context, dockerCli command.Cli, ng *store.NodeGroup, rm bo
|
|||||||
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, false, "")
|
ctx, cancel := context.WithTimeout(ctx, 20*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ll, err := txn.List()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, di := range dis {
|
|
||||||
if di.Driver != nil {
|
builders := make([]*nginfo, len(ll))
|
||||||
if err := di.Driver.Stop(ctx, true); err != nil {
|
for i, ng := range ll {
|
||||||
return err
|
builders[i] = &nginfo{ng: ng}
|
||||||
}
|
|
||||||
if rm {
|
|
||||||
if err := di.Driver.Rm(ctx, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if di.Err != nil {
|
|
||||||
err = di.Err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return err
|
|
||||||
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
|
for _, b := range builders {
|
||||||
|
func(b *nginfo) {
|
||||||
|
eg.Go(func() error {
|
||||||
|
if err := loadNodeGroupData(ctx, dockerCli, b); err != nil {
|
||||||
|
return errors.Wrapf(err, "cannot load %s", b.ng.Name)
|
||||||
|
}
|
||||||
|
if b.ng.Dynamic {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if b.inactive() {
|
||||||
|
rmerr := rm(ctx, dockerCli, in, b.ng)
|
||||||
|
if err := txn.Remove(b.ng.Name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, _ = fmt.Fprintf(dockerCli.Err(), "%s removed\n", b.ng.Name)
|
||||||
|
return rmerr
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,23 +4,60 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
|
|
||||||
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
imagetoolscmd "github.com/docker/buildx/commands/imagetools"
|
||||||
|
"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/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: `"`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logrus.SetFormatter(&logutil.Formatter{})
|
||||||
|
|
||||||
|
logrus.AddHook(logutil.NewFilter([]logrus.Level{
|
||||||
|
logrus.DebugLevel,
|
||||||
|
},
|
||||||
|
"serving grpc connection",
|
||||||
|
"stopping session",
|
||||||
|
"using default config store",
|
||||||
|
))
|
||||||
|
|
||||||
|
// filter out useless commandConn.CloseWrite warning message that can occur
|
||||||
|
// when listing builder instances with "buildx ls" for those that are
|
||||||
|
// unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed"
|
||||||
|
// https://github.com/docker/cli/blob/3fb4fb83dfb5db0c0753a8316f21aea54dab32c5/cli/connhelper/commandconn/commandconn.go#L203-L214
|
||||||
|
logrus.AddHook(logutil.NewFilter([]logrus.Level{
|
||||||
|
logrus.WarnLevel,
|
||||||
|
},
|
||||||
|
"commandConn.CloseWrite:",
|
||||||
|
"commandConn.CloseRead:",
|
||||||
|
))
|
||||||
|
|
||||||
addCommands(cmd, dockerCli)
|
addCommands(cmd, dockerCli)
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
@@ -47,7 +84,7 @@ 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}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/store"
|
||||||
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"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,32 @@ 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)
|
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.builder != "" {
|
||||||
ng, err := getNodeGroup(txn, dockerCli, in.builder)
|
ng, err := storeutil.GetNodeGroup(txn, dockerCli, in.builder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := stop(ctx, dockerCli, ng, false); err != nil {
|
if err := stop(ctx, dockerCli, ng); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ng, err := getCurrentInstance(txn, dockerCli)
|
ng, err := storeutil.GetCurrentInstance(txn, dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if ng != nil {
|
if ng != nil {
|
||||||
return stop(ctx, dockerCli, ng, false)
|
return stop(ctx, dockerCli, ng)
|
||||||
}
|
}
|
||||||
|
|
||||||
return stopCurrent(ctx, dockerCli, false)
|
return stopCurrent(ctx, dockerCli)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
||||||
@@ -58,11 +62,41 @@ func stopCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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, dockerCli command.Cli, ng *store.NodeGroup) error {
|
||||||
|
dis, err := driversForNodeGroup(ctx, dockerCli, ng, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, di := range dis {
|
||||||
|
if di.Driver != nil {
|
||||||
|
if err := di.Driver.Stop(ctx, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if di.Err != nil {
|
||||||
|
err = di.Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopCurrent(ctx context.Context, dockerCli command.Cli) error {
|
||||||
|
dis, err := getDefaultDrivers(ctx, dockerCli, false, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, di := range dis {
|
||||||
|
if di.Driver != nil {
|
||||||
|
if err := di.Driver.Stop(ctx, true); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if di.Err != nil {
|
||||||
|
err = di.Err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
"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"
|
||||||
@@ -54,5 +55,8 @@ func uninstallCmd(dockerCli command.Cli) *cobra.Command {
|
|||||||
Hidden: true,
|
Hidden: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide builder persistent flag for this command
|
||||||
|
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"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 +17,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,7 +29,7 @@ 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 := storeutil.GetCurrentEndpoint(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -51,7 +52,7 @@ 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 := storeutil.GetCurrentEndpoint(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -79,11 +80,8 @@ func useCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|||||||
331
commands/util.go
331
commands/util.go
@@ -2,85 +2,35 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/build"
|
"github.com/docker/buildx/build"
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
|
ctxkube "github.com/docker/buildx/driver/kubernetes/context"
|
||||||
|
remoteutil "github.com/docker/buildx/driver/remote/util"
|
||||||
"github.com/docker/buildx/store"
|
"github.com/docker/buildx/store"
|
||||||
|
"github.com/docker/buildx/store/storeutil"
|
||||||
"github.com/docker/buildx/util/platformutil"
|
"github.com/docker/buildx/util/platformutil"
|
||||||
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/cli/cli/context/docker"
|
"github.com/docker/cli/cli/context/docker"
|
||||||
"github.com/docker/cli/cli/context/kubernetes"
|
ctxstore "github.com/docker/cli/cli/context/store"
|
||||||
dopts "github.com/docker/cli/opts"
|
dopts "github.com/docker/cli/opts"
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
|
"github.com/moby/buildkit/util/grpcerrors"
|
||||||
|
specs "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
)
|
)
|
||||||
|
|
||||||
// getStore returns current builder instance store
|
|
||||||
func getStore(dockerCli command.Cli) (*store.Txn, func(), error) {
|
|
||||||
s, err := store.New(getConfigStorePath(dockerCli))
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return s.Txn()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getConfigStorePath will look for correct configuration store path;
|
|
||||||
// if `$BUILDX_CONFIG` is set - use it, otherwise use parent directory
|
|
||||||
// of Docker config file (i.e. `${DOCKER_CONFIG}/buildx`)
|
|
||||||
func getConfigStorePath(dockerCli command.Cli) string {
|
|
||||||
if buildxConfig := os.Getenv("BUILDX_CONFIG"); buildxConfig != "" {
|
|
||||||
logrus.Debugf("using config store %q based in \"$BUILDX_CONFIG\" environment variable", buildxConfig)
|
|
||||||
return buildxConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
buildxConfig := filepath.Join(filepath.Dir(dockerCli.ConfigFile().Filename), "buildx")
|
|
||||||
logrus.Debugf("using default config store %q", buildxConfig)
|
|
||||||
return buildxConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
// validateEndpoint validates that endpoint is either a context or a docker host
|
||||||
func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
|
func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
|
||||||
de, err := getDockerEndpoint(dockerCli, ep)
|
de, err := storeutil.GetDockerEndpoint(dockerCli, ep)
|
||||||
if err == nil && de != "" {
|
if err == nil && de != "" {
|
||||||
if ep == "default" {
|
if ep == "default" {
|
||||||
return de, nil
|
return de, nil
|
||||||
@@ -94,58 +44,12 @@ func validateEndpoint(dockerCli command.Cli, ep string) (string, error) {
|
|||||||
return h, nil
|
return h, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCurrentInstance finds the current builder instance
|
// validateBuildkitEndpoint validates that endpoint is a valid buildkit host
|
||||||
func getCurrentInstance(txn *store.Txn, dockerCli command.Cli) (*store.NodeGroup, error) {
|
func validateBuildkitEndpoint(ep string) (string, error) {
|
||||||
ep, err := getCurrentEndpoint(dockerCli)
|
if err := remoteutil.IsValidEndpoint(ep); err != nil {
|
||||||
if err != nil {
|
return "", err
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
ng, err := txn.Current(ep)
|
return ep, nil
|
||||||
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
|
// driversForNodeGroup returns drivers for a nodegroup instance
|
||||||
@@ -156,32 +60,49 @@ func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.N
|
|||||||
|
|
||||||
var f driver.Factory
|
var f driver.Factory
|
||||||
if ng.Driver != "" {
|
if ng.Driver != "" {
|
||||||
f = driver.GetFactory(ng.Driver, true)
|
var err error
|
||||||
if f == nil {
|
f, err = driver.GetFactory(ng.Driver, true)
|
||||||
return nil, errors.Errorf("failed to find driver %q", f)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dockerapi, err := clientForEndpoint(dockerCli, ng.Nodes[0].Endpoint)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
f, err = driver.GetDefaultFactory(ctx, dockerapi, false)
|
} 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 := ng.Nodes[0].Endpoint
|
||||||
|
dockerapi, err := clientForEndpoint(dockerCli, ep)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// 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 nil, err
|
||||||
|
}
|
||||||
|
f, err = driver.GetDefaultFactory(ctx, ep, dockerapi, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ng.Driver = f.Name()
|
ng.Driver = f.Name()
|
||||||
}
|
}
|
||||||
|
imageopt, err := storeutil.GetImageConfig(dockerCli, ng)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for i, n := range ng.Nodes {
|
for i, n := range ng.Nodes {
|
||||||
func(i int, n store.Node) {
|
func(i int, n store.Node) {
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
di := build.DriverInfo{
|
di := build.DriverInfo{
|
||||||
Name: n.Name,
|
Name: n.Name,
|
||||||
Platform: n.Platforms,
|
Platform: n.Platforms,
|
||||||
|
ProxyConfig: storeutil.GetProxyConfig(dockerCli),
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
dis[i] = di
|
dis[i] = di
|
||||||
}()
|
}()
|
||||||
|
|
||||||
dockerapi, err := clientForEndpoint(dockerCli, n.Endpoint)
|
dockerapi, err := clientForEndpoint(dockerCli, n.Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
di.Err = err
|
di.Err = err
|
||||||
@@ -193,12 +114,12 @@ func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.N
|
|||||||
contextStore := dockerCli.ContextStore()
|
contextStore := dockerCli.ContextStore()
|
||||||
|
|
||||||
var kcc driver.KubeClientConfig
|
var kcc driver.KubeClientConfig
|
||||||
kcc, err = kubernetes.ConfigFromContext(n.Endpoint, contextStore)
|
kcc, err = configFromContext(n.Endpoint, contextStore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock".
|
// err is returned if n.Endpoint is non-context name like "unix:///var/run/docker.sock".
|
||||||
// try again with name="default".
|
// try again with name="default".
|
||||||
// FIXME: n should retain real context name.
|
// FIXME: n should retain real context name.
|
||||||
kcc, err = kubernetes.ConfigFromContext("default", contextStore)
|
kcc, err = configFromContext("default", contextStore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Error(err)
|
logrus.Error(err)
|
||||||
}
|
}
|
||||||
@@ -220,12 +141,13 @@ func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.N
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, dockerapi, dockerCli.ConfigFile(), kcc, n.Flags, n.ConfigFile, assignDriverOptsByDriverInfo(n.DriverOpts, di), contextPathHash)
|
d, err := driver.GetDriver(ctx, "buildx_buildkit_"+n.Name, f, n.Endpoint, dockerapi, imageopt.Auth, kcc, n.Flags, n.Files, n.DriverOpts, n.Platforms, contextPathHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
di.Err = err
|
di.Err = err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
di.Driver = d
|
di.Driver = d
|
||||||
|
di.ImageOpt = imageopt
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}(i, n)
|
}(i, n)
|
||||||
@@ -238,18 +160,20 @@ func driversForNodeGroup(ctx context.Context, dockerCli command.Cli, ng *store.N
|
|||||||
return dis, nil
|
return dis, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// pass platform as driver opts to provide for some drive, like kubernetes
|
func configFromContext(endpointName string, s ctxstore.Reader) (clientcmd.ClientConfig, error) {
|
||||||
func assignDriverOptsByDriverInfo(opts map[string]string, driveInfo build.DriverInfo) map[string]string {
|
if strings.HasPrefix(endpointName, "kubernetes://") {
|
||||||
m := map[string]string{}
|
u, _ := url.Parse(endpointName)
|
||||||
|
if kubeconfig := u.Query().Get("kubeconfig"); kubeconfig != "" {
|
||||||
if len(driveInfo.Platform) > 0 {
|
_ = os.Setenv(clientcmd.RecommendedConfigPathEnvVar, kubeconfig)
|
||||||
m["platform"] = strings.Join(platformutil.Format(driveInfo.Platform), ",")
|
}
|
||||||
|
rules := clientcmd.NewDefaultClientConfigLoadingRules()
|
||||||
|
apiConfig, err := rules.Load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return clientcmd.NewDefaultClientConfig(*apiConfig, &clientcmd.ConfigOverrides{}), nil
|
||||||
}
|
}
|
||||||
|
return ctxkube.ConfigFromContext(endpointName, s)
|
||||||
for key := range opts {
|
|
||||||
m[key] = opts[key]
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// clientForEndpoint returns a docker client for an endpoint
|
// clientForEndpoint returns a docker client for an endpoint
|
||||||
@@ -321,7 +245,7 @@ func getInstanceOrDefault(ctx context.Context, dockerCli command.Cli, instance,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getInstanceByName(ctx context.Context, dockerCli command.Cli, instance, contextPathHash string) ([]build.DriverInfo, error) {
|
func getInstanceByName(ctx context.Context, dockerCli command.Cli, instance, contextPathHash string) ([]build.DriverInfo, error) {
|
||||||
txn, release, err := getStore(dockerCli)
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -336,14 +260,14 @@ func getInstanceByName(ctx context.Context, dockerCli command.Cli, instance, con
|
|||||||
|
|
||||||
// getDefaultDrivers returns drivers based on current cli config
|
// getDefaultDrivers returns drivers based on current cli config
|
||||||
func getDefaultDrivers(ctx context.Context, dockerCli command.Cli, defaultOnly bool, contextPathHash string) ([]build.DriverInfo, error) {
|
func getDefaultDrivers(ctx context.Context, dockerCli command.Cli, defaultOnly bool, contextPathHash string) ([]build.DriverInfo, error) {
|
||||||
txn, release, err := getStore(dockerCli)
|
txn, release, err := storeutil.GetStore(dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
if !defaultOnly {
|
if !defaultOnly {
|
||||||
ng, err := getCurrentInstance(txn, dockerCli)
|
ng, err := storeutil.GetCurrentInstance(txn, dockerCli)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -353,14 +277,21 @@ func getDefaultDrivers(ctx context.Context, dockerCli command.Cli, defaultOnly b
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, dockerCli.Client(), dockerCli.ConfigFile(), nil, nil, "", nil, contextPathHash)
|
imageopt, err := storeutil.GetImageConfig(dockerCli, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
d, err := driver.GetDriver(ctx, "buildx_buildkit_default", nil, "", dockerCli.Client(), imageopt.Auth, nil, nil, nil, nil, nil, contextPathHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return []build.DriverInfo{
|
return []build.DriverInfo{
|
||||||
{
|
{
|
||||||
Name: "default",
|
Name: "default",
|
||||||
Driver: d,
|
Driver: d,
|
||||||
|
ImageOpt: imageopt,
|
||||||
|
ProxyConfig: storeutil.GetProxyConfig(dockerCli),
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@@ -384,11 +315,20 @@ func loadInfoData(ctx context.Context, d *dinfo) error {
|
|||||||
return errors.Wrap(err, "listing workers")
|
return errors.Wrap(err, "listing workers")
|
||||||
}
|
}
|
||||||
for _, w := range workers {
|
for _, w := range workers {
|
||||||
for _, p := range w.Platforms {
|
d.platforms = append(d.platforms, w.Platforms...)
|
||||||
d.platforms = append(d.platforms, p)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
d.platforms = platformutil.Dedupe(d.platforms)
|
d.platforms = platformutil.Dedupe(d.platforms)
|
||||||
|
inf, err := c.Info(ctx)
|
||||||
|
if err != nil {
|
||||||
|
if st, ok := grpcerrors.AsGRPCStatus(err); ok && st.Code() == codes.Unimplemented {
|
||||||
|
d.version, err = d.di.Driver.Version(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "getting version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
d.version = inf.BuildkitVersion.Version
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -418,13 +358,24 @@ func loadNodeGroupData(ctx context.Context, dockerCli command.Cli, ngi *nginfo)
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip when multi drivers
|
kubernetesDriverCount := 0
|
||||||
if len(ngi.drivers) == 1 {
|
|
||||||
|
for _, di := range ngi.drivers {
|
||||||
|
if di.info != nil && len(di.info.DynamicNodes) > 0 {
|
||||||
|
kubernetesDriverCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isAllKubernetesDrivers := len(ngi.drivers) == kubernetesDriverCount
|
||||||
|
|
||||||
|
if isAllKubernetesDrivers {
|
||||||
|
var drivers []dinfo
|
||||||
|
var dynamicNodes []store.Node
|
||||||
|
|
||||||
for _, di := range ngi.drivers {
|
for _, di := range ngi.drivers {
|
||||||
// dynamic nodes are used in Kubernetes driver.
|
// dynamic nodes are used in Kubernetes driver.
|
||||||
// Kubernetes pods are dynamically mapped to BuildKit Nodes.
|
// Kubernetes pods are dynamically mapped to BuildKit Nodes.
|
||||||
if di.info != nil && len(di.info.DynamicNodes) > 0 {
|
if di.info != nil && len(di.info.DynamicNodes) > 0 {
|
||||||
var drivers []dinfo
|
|
||||||
for i := 0; i < len(di.info.DynamicNodes); i++ {
|
for i := 0; i < len(di.info.DynamicNodes); i++ {
|
||||||
// all []dinfo share *build.DriverInfo and *driver.Info
|
// all []dinfo share *build.DriverInfo and *driver.Info
|
||||||
diClone := di
|
diClone := di
|
||||||
@@ -433,17 +384,28 @@ func loadNodeGroupData(ctx context.Context, dockerCli command.Cli, ngi *nginfo)
|
|||||||
}
|
}
|
||||||
drivers = append(drivers, di)
|
drivers = append(drivers, di)
|
||||||
}
|
}
|
||||||
// not append (remove the static nodes in the store)
|
dynamicNodes = append(dynamicNodes, di.info.DynamicNodes...)
|
||||||
ngi.ng.Nodes = di.info.DynamicNodes
|
|
||||||
ngi.ng.Dynamic = true
|
|
||||||
ngi.drivers = drivers
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// not append (remove the static nodes in the store)
|
||||||
|
ngi.ng.Nodes = dynamicNodes
|
||||||
|
ngi.drivers = drivers
|
||||||
|
ngi.ng.Dynamic = true
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasNodeGroup(list []*nginfo, ngi *nginfo) bool {
|
||||||
|
for _, l := range list {
|
||||||
|
if ngi.ng.Name == l.ng.Name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func dockerAPI(dockerCli command.Cli) *api {
|
func dockerAPI(dockerCli command.Cli) *api {
|
||||||
return &api{dockerCli: dockerCli}
|
return &api{dockerCli: dockerCli}
|
||||||
}
|
}
|
||||||
@@ -458,3 +420,68 @@ func (a *api) DockerAPI(name string) (dockerclient.APIClient, error) {
|
|||||||
}
|
}
|
||||||
return clientForEndpoint(a.dockerCli, name)
|
return clientForEndpoint(a.dockerCli, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type dinfo struct {
|
||||||
|
di *build.DriverInfo
|
||||||
|
info *driver.Info
|
||||||
|
platforms []specs.Platform
|
||||||
|
version string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type nginfo struct {
|
||||||
|
ng *store.NodeGroup
|
||||||
|
drivers []dinfo
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// inactive checks if all nodes are inactive for this builder
|
||||||
|
func (n *nginfo) inactive() bool {
|
||||||
|
for idx := range n.ng.Nodes {
|
||||||
|
d := n.drivers[idx]
|
||||||
|
if d.info != nil && d.info.Status == driver.Running {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func boot(ctx context.Context, ngi *nginfo) (bool, error) {
|
||||||
|
toBoot := make([]int, 0, len(ngi.drivers))
|
||||||
|
for i, d := range ngi.drivers {
|
||||||
|
if d.err != nil || d.di.Err != nil || d.di.Driver == nil || d.info == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if d.info.Status != driver.Running {
|
||||||
|
toBoot = append(toBoot, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(toBoot) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
printer := progress.NewPrinter(context.TODO(), os.Stderr, os.Stderr, "auto")
|
||||||
|
|
||||||
|
baseCtx := ctx
|
||||||
|
eg, _ := errgroup.WithContext(ctx)
|
||||||
|
for _, idx := range toBoot {
|
||||||
|
func(idx int) {
|
||||||
|
eg.Go(func() error {
|
||||||
|
pw := progress.WithPrefix(printer, ngi.ng.Nodes[idx].Name, len(toBoot) > 1)
|
||||||
|
_, err := driver.Boot(ctx, baseCtx, ngi.drivers[idx].di.Driver, pw)
|
||||||
|
if err != nil {
|
||||||
|
ngi.drivers[idx].err = err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
err := eg.Wait()
|
||||||
|
err1 := printer.Wait()
|
||||||
|
if err == nil {
|
||||||
|
err = err1
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package commands
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/util/cobrautil"
|
||||||
"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 +18,15 @@ 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)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hide builder persistent flag for this command
|
||||||
|
cobrautil.HideInheritedFlags(cmd, "builder")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
146
docker-bake.hcl
Normal file
146
docker-bake.hcl
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
variable "GO_VERSION" {
|
||||||
|
default = "1.18"
|
||||||
|
}
|
||||||
|
variable "BIN_OUT" {
|
||||||
|
default = "./bin"
|
||||||
|
}
|
||||||
|
variable "RELEASE_OUT" {
|
||||||
|
default = "./release-out"
|
||||||
|
}
|
||||||
|
variable "DOCS_FORMATS" {
|
||||||
|
default = "md"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
BUILDX_EXPERIMENTAL = 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
|
||||||
|
}
|
||||||
|
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 "update-vendor" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
dockerfile = "./hack/dockerfiles/vendor.Dockerfile"
|
||||||
|
target = "update"
|
||||||
|
output = ["."]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "update-docs" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
args = {
|
||||||
|
FORMATS = DOCS_FORMATS
|
||||||
|
}
|
||||||
|
dockerfile = "./hack/dockerfiles/docs.Dockerfile"
|
||||||
|
target = "update"
|
||||||
|
output = ["./docs/reference"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "update-authors" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
dockerfile = "./hack/dockerfiles/authors.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 = ["./coverage"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "binaries" {
|
||||||
|
inherits = ["_common"]
|
||||||
|
target = "binaries"
|
||||||
|
output = [BIN_OUT]
|
||||||
|
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 = [RELEASE_OUT]
|
||||||
|
}
|
||||||
|
|
||||||
|
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"]
|
||||||
|
}
|
||||||
90
docs/generate.go
Normal file
90
docs/generate.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/docker/buildx/commands"
|
||||||
|
clidocstool "github.com/docker/cli-docs-tool"
|
||||||
|
"github.com/docker/cli/cli/command"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
// import drivers otherwise factories are empty
|
||||||
|
// for --driver output flag usage
|
||||||
|
_ "github.com/docker/buildx/driver/docker"
|
||||||
|
_ "github.com/docker/buildx/driver/docker-container"
|
||||||
|
_ "github.com/docker/buildx/driver/kubernetes"
|
||||||
|
_ "github.com/docker/buildx/driver/remote"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultSourcePath = "docs/reference/"
|
||||||
|
|
||||||
|
type options struct {
|
||||||
|
source string
|
||||||
|
formats []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func gen(opts *options) error {
|
||||||
|
log.SetFlags(0)
|
||||||
|
|
||||||
|
dockerCLI, err := command.NewDockerCli()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "docker [OPTIONS] COMMAND [ARG...]",
|
||||||
|
Short: "The base command for the Docker CLI.",
|
||||||
|
DisableAutoGenTag: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(commands.NewRootCmd("buildx", true, dockerCLI))
|
||||||
|
|
||||||
|
c, err := clidocstool.New(clidocstool.Options{
|
||||||
|
Root: cmd,
|
||||||
|
SourceDir: opts.source,
|
||||||
|
Plugin: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, format := range opts.formats {
|
||||||
|
switch format {
|
||||||
|
case "md":
|
||||||
|
if err = c.GenMarkdownTree(cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "yaml":
|
||||||
|
if err = c.GenYamlTree(cmd); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.Errorf("unknown format %q", format)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func run() error {
|
||||||
|
opts := &options{}
|
||||||
|
flags := pflag.NewFlagSet(os.Args[0], pflag.ContinueOnError)
|
||||||
|
flags.StringVar(&opts.source, "source", defaultSourcePath, "Docs source folder")
|
||||||
|
flags.StringSliceVar(&opts.formats, "formats", []string{}, "Format (md, yaml)")
|
||||||
|
if err := flags.Parse(os.Args[1:]); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(opts.formats) == 0 {
|
||||||
|
return errors.New("Docs format required")
|
||||||
|
}
|
||||||
|
return gen(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if err := run(); err != nil {
|
||||||
|
log.Printf("ERROR: %+v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
74
docs/guides/bake/build-contexts.md
Normal file
74
docs/guides/bake/build-contexts.md
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# Defining additional build contexts and linking targets
|
||||||
|
|
||||||
|
In addition to the main `context` key that defines the build context each target
|
||||||
|
can also define additional named contexts with a map defined with key `contexts`.
|
||||||
|
These values map to the `--build-context` flag in the [build command](https://docs.docker.com/engine/reference/commandline/buildx_build/#build-context).
|
||||||
|
|
||||||
|
Inside the Dockerfile these contexts can be used with the `FROM` instruction or `--from` flag.
|
||||||
|
|
||||||
|
The value can be a local source directory, container image (with `docker-image://` prefix),
|
||||||
|
Git URL, HTTP URL or a name of another target in the Bake file (with `target:` prefix).
|
||||||
|
|
||||||
|
## Pinning alpine image
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM alpine
|
||||||
|
RUN echo "Hello world"
|
||||||
|
```
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "app" {
|
||||||
|
contexts = {
|
||||||
|
alpine = "docker-image://alpine:3.13"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using a secondary source directory
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM scratch AS src
|
||||||
|
|
||||||
|
FROM golang
|
||||||
|
COPY --from=src . .
|
||||||
|
```
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "app" {
|
||||||
|
contexts = {
|
||||||
|
src = "../path/to/source"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using a result of one target as a base image in another target
|
||||||
|
|
||||||
|
To use a result of one target as a build context of another, specity the target
|
||||||
|
name with `target:` prefix.
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
FROM baseapp
|
||||||
|
RUN echo "Hello world"
|
||||||
|
```
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "base" {
|
||||||
|
dockerfile = "baseapp.Dockerfile"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app" {
|
||||||
|
contexts = {
|
||||||
|
baseapp = "target:base"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Please note that in most cases you should just use a single multi-stage
|
||||||
|
Dockerfile with multiple targets for similar behavior. This case is recommended
|
||||||
|
when you have multiple Dockerfiles that can't be easily merged into one.
|
||||||
270
docs/guides/bake/compose-file.md
Normal file
270
docs/guides/bake/compose-file.md
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
# Building from Compose file
|
||||||
|
|
||||||
|
## Specification
|
||||||
|
|
||||||
|
Bake uses the [compose-spec](https://docs.docker.com/compose/compose-file/) to
|
||||||
|
parse a compose file and translate each service to a [target](file-definition.md#target).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
services:
|
||||||
|
webapp-dev:
|
||||||
|
build: &build-dev
|
||||||
|
dockerfile: Dockerfile.webapp
|
||||||
|
tags:
|
||||||
|
- docker.io/username/webapp:latest
|
||||||
|
cache_from:
|
||||||
|
- docker.io/username/webapp:cache
|
||||||
|
cache_to:
|
||||||
|
- docker.io/username/webapp:cache
|
||||||
|
|
||||||
|
webapp-release:
|
||||||
|
build:
|
||||||
|
<<: *build-dev
|
||||||
|
x-bake:
|
||||||
|
platforms:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: docker.io/username/db
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile.db
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"db",
|
||||||
|
"webapp-dev",
|
||||||
|
"webapp-release"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"db": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile.db",
|
||||||
|
"tags": [
|
||||||
|
"docker.io/username/db"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"webapp-dev": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile.webapp",
|
||||||
|
"tags": [
|
||||||
|
"docker.io/username/webapp:latest"
|
||||||
|
],
|
||||||
|
"cache-from": [
|
||||||
|
"docker.io/username/webapp:cache"
|
||||||
|
],
|
||||||
|
"cache-to": [
|
||||||
|
"docker.io/username/webapp:cache"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"webapp-release": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile.webapp",
|
||||||
|
"tags": [
|
||||||
|
"docker.io/username/webapp:latest"
|
||||||
|
],
|
||||||
|
"cache-from": [
|
||||||
|
"docker.io/username/webapp:cache"
|
||||||
|
],
|
||||||
|
"cache-to": [
|
||||||
|
"docker.io/username/webapp:cache"
|
||||||
|
],
|
||||||
|
"platforms": [
|
||||||
|
"linux/amd64",
|
||||||
|
"linux/arm64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Unlike the [HCL format](file-definition.md#hcl-definition), there are some
|
||||||
|
limitations with the compose format:
|
||||||
|
|
||||||
|
* Specifying variables or global scope attributes is not yet supported
|
||||||
|
* `inherits` service field is not supported, but you can use [YAML anchors](https://docs.docker.com/compose/compose-file/#fragments) to reference other services like the example above
|
||||||
|
|
||||||
|
## `.env` file
|
||||||
|
|
||||||
|
You can declare default environment variables in an environment file named
|
||||||
|
`.env`. This file will be loaded from the current working directory,
|
||||||
|
where the command is executed and applied to compose definitions passed
|
||||||
|
with `-f`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
services:
|
||||||
|
webapp:
|
||||||
|
image: docker.io/username/webapp:${TAG:-v1.0.0}
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
# .env
|
||||||
|
TAG=v1.1.0
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"docker.io/username/webapp:v1.1.0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> System environment variables take precedence over environment variables
|
||||||
|
> in `.env` file.
|
||||||
|
|
||||||
|
## Extension field with `x-bake`
|
||||||
|
|
||||||
|
Even if some fields are not (yet) available in the compose specification, you
|
||||||
|
can use the [special extension](https://docs.docker.com/compose/compose-file/#extension)
|
||||||
|
field `x-bake` in your compose file to evaluate extra fields:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
services:
|
||||||
|
addon:
|
||||||
|
image: ct-addon:bar
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
args:
|
||||||
|
CT_ECR: foo
|
||||||
|
CT_TAG: bar
|
||||||
|
x-bake:
|
||||||
|
tags:
|
||||||
|
- ct-addon:foo
|
||||||
|
- ct-addon:alp
|
||||||
|
platforms:
|
||||||
|
- linux/amd64
|
||||||
|
- linux/arm64
|
||||||
|
cache-from:
|
||||||
|
- user/app:cache
|
||||||
|
- 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=./secret
|
||||||
|
- id=mysecret2,src=./secret2
|
||||||
|
platforms: linux/arm64
|
||||||
|
output: type=docker
|
||||||
|
no-cache: true
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"aws",
|
||||||
|
"addon"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"addon": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "./Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"CT_ECR": "foo",
|
||||||
|
"CT_TAG": "bar"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"ct-addon:foo",
|
||||||
|
"ct-addon:alp"
|
||||||
|
],
|
||||||
|
"cache-from": [
|
||||||
|
"user/app:cache",
|
||||||
|
"type=local,src=path/to/cache"
|
||||||
|
],
|
||||||
|
"cache-to": [
|
||||||
|
"type=local,dest=path/to/cache"
|
||||||
|
],
|
||||||
|
"platforms": [
|
||||||
|
"linux/amd64",
|
||||||
|
"linux/arm64"
|
||||||
|
],
|
||||||
|
"pull": true
|
||||||
|
},
|
||||||
|
"aws": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "./aws.Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"CT_ECR": "foo",
|
||||||
|
"CT_TAG": "bar"
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"ct-fake-aws:bar"
|
||||||
|
],
|
||||||
|
"secret": [
|
||||||
|
"id=mysecret,src=./secret",
|
||||||
|
"id=mysecret2,src=./secret2"
|
||||||
|
],
|
||||||
|
"platforms": [
|
||||||
|
"linux/arm64"
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
"type=docker"
|
||||||
|
],
|
||||||
|
"no-cache": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Complete list of valid fields for `x-bake`:
|
||||||
|
|
||||||
|
* `cache-from`
|
||||||
|
* `cache-to`
|
||||||
|
* `contexts`
|
||||||
|
* `no-cache`
|
||||||
|
* `no-cache-filter`
|
||||||
|
* `output`
|
||||||
|
* `platforms`
|
||||||
|
* `pull`
|
||||||
|
* `secret`
|
||||||
|
* `ssh`
|
||||||
|
* `tags`
|
||||||
216
docs/guides/bake/configuring-build.md
Normal file
216
docs/guides/bake/configuring-build.md
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
# Configuring builds
|
||||||
|
|
||||||
|
Bake supports loading build definition from files, but sometimes you need even
|
||||||
|
more flexibility to configure this definition.
|
||||||
|
|
||||||
|
For this use case, you can define variables inside the bake files that can be
|
||||||
|
set by the user with environment variables or by [attribute definitions](#global-scope-attributes)
|
||||||
|
in other bake files. If you wish to change a specific value for a single
|
||||||
|
invocation you can use the `--set` flag [from the command line](#from-command-line).
|
||||||
|
|
||||||
|
## Global scope attributes
|
||||||
|
|
||||||
|
You can define global scope attributes in HCL/JSON and use them for code reuse
|
||||||
|
and setting values for variables. This means you can do a "data-only" HCL file
|
||||||
|
with the values you want to set/override and use it in the list of regular
|
||||||
|
output files.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "FOO" {
|
||||||
|
default = "abc"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v1 = "pre-${FOO}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use this file directly:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print app
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"app"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"app": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"v1": "pre-abc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or create an override configuration file:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# env.hcl
|
||||||
|
WHOAMI="myuser"
|
||||||
|
FOO="def-${WHOAMI}"
|
||||||
|
```
|
||||||
|
|
||||||
|
And invoke bake together with both of the files:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake -f docker-bake.hcl -f env.hcl --print app
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"app"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"app": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"v1": "pre-def-myuser"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## From command line
|
||||||
|
|
||||||
|
You can also override target configurations from the command line with the
|
||||||
|
[`--set` flag](https://docs.docker.com/engine/reference/commandline/buildx_bake/#set):
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
mybuildarg = "foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --set app.args.mybuildarg=bar --set app.platform=linux/arm64 app --print
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"app"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"app": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"mybuildarg": "bar"
|
||||||
|
},
|
||||||
|
"platforms": [
|
||||||
|
"linux/arm64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Pattern matching syntax defined in [https://golang.org/pkg/path/#Match](https://golang.org/pkg/path/#Match)
|
||||||
|
is also supported:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --set foo*.args.mybuildarg=value # overrides build arg for all targets starting with "foo"
|
||||||
|
$ docker buildx bake --set *.platform=linux/arm64 # overrides platform for all targets
|
||||||
|
$ docker buildx bake --set foo*.no-cache # bypass caching only for targets starting with "foo"
|
||||||
|
```
|
||||||
|
|
||||||
|
Complete list of overridable fields:
|
||||||
|
|
||||||
|
* `args`
|
||||||
|
* `cache-from`
|
||||||
|
* `cache-to`
|
||||||
|
* `context`
|
||||||
|
* `dockerfile`
|
||||||
|
* `labels`
|
||||||
|
* `no-cache`
|
||||||
|
* `output`
|
||||||
|
* `platform`
|
||||||
|
* `pull`
|
||||||
|
* `secrets`
|
||||||
|
* `ssh`
|
||||||
|
* `tags`
|
||||||
|
* `target`
|
||||||
|
|
||||||
|
## Using variables in variables across files
|
||||||
|
|
||||||
|
When multiple files are specified, one file can use variables defined in
|
||||||
|
another file.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake1.hcl
|
||||||
|
variable "FOO" {
|
||||||
|
default = upper("${BASE}def")
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "BAR" {
|
||||||
|
default = "-${FOO}-"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v1 = "pre-${BAR}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake2.hcl
|
||||||
|
variable "BASE" {
|
||||||
|
default = "abc"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v2 = "${FOO}-post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake -f docker-bake1.hcl -f docker-bake2.hcl --print app
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"app"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"app": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"v1": "pre--ABCDEF-",
|
||||||
|
"v2": "ABCDEF-post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
440
docs/guides/bake/file-definition.md
Normal file
440
docs/guides/bake/file-definition.md
Normal file
@@ -0,0 +1,440 @@
|
|||||||
|
# Bake file definition
|
||||||
|
|
||||||
|
`buildx bake` supports HCL, JSON and Compose file format for defining build
|
||||||
|
[groups](#group), [targets](#target) as well as [variables](#variable) and
|
||||||
|
[functions](#functions). It looks for build definition files in the current
|
||||||
|
directory in the following order:
|
||||||
|
|
||||||
|
* `docker-compose.yml`
|
||||||
|
* `docker-compose.yaml`
|
||||||
|
* `docker-bake.json`
|
||||||
|
* `docker-bake.override.json`
|
||||||
|
* `docker-bake.hcl`
|
||||||
|
* `docker-bake.override.hcl`
|
||||||
|
|
||||||
|
## Specification
|
||||||
|
|
||||||
|
Inside a bake file you can declare group, target and variable blocks to define
|
||||||
|
project specific reusable build flows.
|
||||||
|
|
||||||
|
### Target
|
||||||
|
|
||||||
|
A target reflects a single docker build invocation with the same options that
|
||||||
|
you would specify for `docker build`:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:latest"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```console
|
||||||
|
$ docker buildx bake webapp-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> In the case of compose files, each service corresponds to a target.
|
||||||
|
> If compose service name contains a dot it will be replaced with an underscore.
|
||||||
|
|
||||||
|
Complete list of valid target fields available for [HCL](#hcl-definition) and
|
||||||
|
[JSON](#json-definition) definitions:
|
||||||
|
|
||||||
|
| Name | Type | Description |
|
||||||
|
|---------------------|--------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| `inherits` | List | [Inherit build options](#merging-and-inheritance) from other targets |
|
||||||
|
| `args` | Map | Set build-time variables (same as [`--build-arg` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `cache-from` | List | External cache sources (same as [`--cache-from` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `cache-to` | List | Cache export destinations (same as [`--cache-to` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `context` | String | Set of files located in the specified path or URL |
|
||||||
|
| `contexts` | Map | Additional build contexts (same as [`--build-context` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `dockerfile` | String | Name of the Dockerfile (same as [`--file` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `dockerfile-inline` | String | Inline Dockerfile content |
|
||||||
|
| `labels` | Map | Set metadata for an image (same as [`--label` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `no-cache` | Bool | Do not use cache when building the image (same as [`--no-cache` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `no-cache-filter` | List | Do not cache specified stages (same as [`--no-cache-filter` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `output` | List | Output destination (same as [`--output` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `platforms` | List | Set target platforms for build (same as [`--platform` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `pull` | Bool | Always attempt to pull all referenced images (same as [`--pull` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `secret` | List | Secret to expose to the build (same as [`--secret` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `ssh` | List | SSH agent socket or keys to expose to the build (same as [`--ssh` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `tags` | List | Name and optionally a tag in the format `name:tag` (same as [`--tag` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
| `target` | String | Set the target build stage to build (same as [`--target` flag](https://docs.docker.com/engine/reference/commandline/buildx_build/)) |
|
||||||
|
|
||||||
|
### Group
|
||||||
|
|
||||||
|
A group is a grouping of targets:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
group "build" {
|
||||||
|
targets = ["db", "webapp-dev"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:latest"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "db" {
|
||||||
|
dockerfile = "Dockerfile.db"
|
||||||
|
tags = ["docker.io/username/db"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```console
|
||||||
|
$ docker buildx bake build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variable
|
||||||
|
|
||||||
|
Similar to how Terraform provides a way to [define variables](https://www.terraform.io/docs/configuration/variables.html#declaring-an-input-variable),
|
||||||
|
the HCL file format also supports variable block definitions. These can be used
|
||||||
|
to define variables with values provided by the current environment, or a
|
||||||
|
default value when unset:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "TAG" {
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:${TAG}"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```console
|
||||||
|
$ docker buildx bake webapp-dev # will use the default value "latest"
|
||||||
|
$ TAG=dev docker buildx bake webapp-dev # will use the TAG environment variable value
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Tip**
|
||||||
|
>
|
||||||
|
> See also the [Configuring builds](configuring-build.md) page for advanced usage.
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
|
A [set of generally useful functions](https://github.com/docker/buildx/blob/master/bake/hclparser/stdlib.go)
|
||||||
|
provided by [go-cty](https://github.com/zclconf/go-cty/tree/main/cty/function/stdlib)
|
||||||
|
are available for use in HCL files:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:latest"]
|
||||||
|
args = {
|
||||||
|
buildno = "${add(123, 1)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In addition, [user defined functions](https://github.com/hashicorp/hcl/tree/main/ext/userfunc)
|
||||||
|
are also supported:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
function "increment" {
|
||||||
|
params = [number]
|
||||||
|
result = number + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:latest"]
|
||||||
|
args = {
|
||||||
|
buildno = "${increment(123)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> See [User defined HCL functions](hcl-funcs.md) page for more details.
|
||||||
|
|
||||||
|
## Built-in variables
|
||||||
|
|
||||||
|
* `BAKE_CMD_CONTEXT` can be used to access the main `context` for bake command
|
||||||
|
from a bake file that has been [imported remotely](file-definition.md#remote-definition).
|
||||||
|
* `BAKE_LOCAL_PLATFORM` returns the current platform's default platform
|
||||||
|
specification (e.g. `linux/amd64`).
|
||||||
|
|
||||||
|
## Merging and inheritance
|
||||||
|
|
||||||
|
Multiple files can include the same target and final build options will be
|
||||||
|
determined by merging them together:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:latest"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```hcl
|
||||||
|
# docker-bake2.hcl
|
||||||
|
target "webapp-dev" {
|
||||||
|
tags = ["docker.io/username/webapp:dev"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```console
|
||||||
|
$ docker buildx bake -f docker-bake.hcl -f docker-bake2.hcl webapp-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
A group can specify its list of targets with the `targets` option. A target can
|
||||||
|
inherit build options by setting the `inherits` option to the list of targets or
|
||||||
|
groups to inherit from:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:${TAG}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-release" {
|
||||||
|
inherits = ["webapp-dev"]
|
||||||
|
platforms = ["linux/amd64", "linux/arm64"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## `default` target/group
|
||||||
|
|
||||||
|
When you invoke `bake` you specify what targets/groups you want to build. If no
|
||||||
|
arguments is specified, the group/target named `default` will be built:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
target "default" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:latest"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```console
|
||||||
|
$ docker buildx bake
|
||||||
|
```
|
||||||
|
|
||||||
|
## Definitions
|
||||||
|
|
||||||
|
### HCL definition
|
||||||
|
|
||||||
|
HCL definition file is recommended as its experience is more aligned with buildx UX
|
||||||
|
and also allows better code reuse, different target groups and extended features.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "TAG" {
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = ["db", "webapp-dev"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp:${TAG}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-release" {
|
||||||
|
inherits = ["webapp-dev"]
|
||||||
|
platforms = ["linux/amd64", "linux/arm64"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "db" {
|
||||||
|
dockerfile = "Dockerfile.db"
|
||||||
|
tags = ["docker.io/username/db"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSON definition
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"variable": {
|
||||||
|
"TAG": {
|
||||||
|
"default": "latest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"db",
|
||||||
|
"webapp-dev"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp-dev": {
|
||||||
|
"dockerfile": "Dockerfile.webapp",
|
||||||
|
"tags": [
|
||||||
|
"docker.io/username/webapp:${TAG}"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"webapp-release": {
|
||||||
|
"inherits": [
|
||||||
|
"webapp-dev"
|
||||||
|
],
|
||||||
|
"platforms": [
|
||||||
|
"linux/amd64",
|
||||||
|
"linux/arm64"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"db": {
|
||||||
|
"dockerfile": "Dockerfile.db",
|
||||||
|
"tags": [
|
||||||
|
"docker.io/username/db"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compose file
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
services:
|
||||||
|
webapp:
|
||||||
|
image: docker.io/username/webapp:latest
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile.webapp
|
||||||
|
|
||||||
|
db:
|
||||||
|
image: docker.io/username/db
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile.db
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> See [Building from Compose file](compose-file.md) page for more details.
|
||||||
|
|
||||||
|
## Remote definition
|
||||||
|
|
||||||
|
You can also build bake files directly from a remote Git repository or HTTPS URL:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake "https://github.com/docker/cli.git#v20.10.11" --print
|
||||||
|
#1 [internal] load git source https://github.com/docker/cli.git#v20.10.11
|
||||||
|
#1 0.745 e8f1871b077b64bcb4a13334b7146492773769f7 refs/tags/v20.10.11
|
||||||
|
#1 2.022 From https://github.com/docker/cli
|
||||||
|
#1 2.022 * [new tag] v20.10.11 -> v20.10.11
|
||||||
|
#1 DONE 2.9s
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"binary"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"binary": {
|
||||||
|
"context": "https://github.com/docker/cli.git#v20.10.11",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"BASE_VARIANT": "alpine",
|
||||||
|
"GO_STRIP": "",
|
||||||
|
"VERSION": ""
|
||||||
|
},
|
||||||
|
"target": "binary",
|
||||||
|
"platforms": [
|
||||||
|
"local"
|
||||||
|
],
|
||||||
|
"output": [
|
||||||
|
"build"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see the context is fixed to `https://github.com/docker/cli.git` even if
|
||||||
|
[no context is actually defined](https://github.com/docker/cli/blob/2776a6d694f988c0c1df61cad4bfac0f54e481c8/docker-bake.hcl#L17-L26)
|
||||||
|
in the definition.
|
||||||
|
|
||||||
|
If you want to access the main context for bake command from a bake file
|
||||||
|
that has been imported remotely, you can use the [`BAKE_CMD_CONTEXT` built-in var](#built-in-variables).
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ cat https://raw.githubusercontent.com/tonistiigi/buildx/remote-test/docker-bake.hcl
|
||||||
|
```
|
||||||
|
```hcl
|
||||||
|
target "default" {
|
||||||
|
context = BAKE_CMD_CONTEXT
|
||||||
|
dockerfile-inline = <<EOT
|
||||||
|
FROM alpine
|
||||||
|
WORKDIR /src
|
||||||
|
COPY . .
|
||||||
|
RUN ls -l && stop
|
||||||
|
EOT
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" --print
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"default": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"dockerfile-inline": "FROM alpine\nWORKDIR /src\nCOPY . .\nRUN ls -l \u0026\u0026 stop\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ touch foo bar
|
||||||
|
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test"
|
||||||
|
```
|
||||||
|
```text
|
||||||
|
...
|
||||||
|
> [4/4] RUN ls -l && stop:
|
||||||
|
#8 0.101 total 0
|
||||||
|
#8 0.102 -rw-r--r-- 1 root root 0 Jul 27 18:47 bar
|
||||||
|
#8 0.102 -rw-r--r-- 1 root root 0 Jul 27 18:47 foo
|
||||||
|
#8 0.102 /bin/sh: stop: not found
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" "https://github.com/docker/cli.git#v20.10.11" --print
|
||||||
|
#1 [internal] load git source https://github.com/tonistiigi/buildx.git#remote-test
|
||||||
|
#1 0.429 577303add004dd7efeb13434d69ea030d35f7888 refs/heads/remote-test
|
||||||
|
#1 CACHED
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"target": {
|
||||||
|
"default": {
|
||||||
|
"context": "https://github.com/docker/cli.git#v20.10.11",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"dockerfile-inline": "FROM alpine\nWORKDIR /src\nCOPY . .\nRUN ls -l \u0026\u0026 stop\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake "https://github.com/tonistiigi/buildx.git#remote-test" "https://github.com/docker/cli.git#v20.10.11"
|
||||||
|
```
|
||||||
|
```text
|
||||||
|
...
|
||||||
|
> [4/4] RUN ls -l && stop:
|
||||||
|
#8 0.136 drwxrwxrwx 5 root root 4096 Jul 27 18:31 kubernetes
|
||||||
|
#8 0.136 drwxrwxrwx 3 root root 4096 Jul 27 18:31 man
|
||||||
|
#8 0.136 drwxrwxrwx 2 root root 4096 Jul 27 18:31 opts
|
||||||
|
#8 0.136 -rw-rw-rw- 1 root root 1893 Jul 27 18:31 poule.yml
|
||||||
|
#8 0.136 drwxrwxrwx 7 root root 4096 Jul 27 18:31 scripts
|
||||||
|
#8 0.136 drwxrwxrwx 3 root root 4096 Jul 27 18:31 service
|
||||||
|
#8 0.136 drwxrwxrwx 2 root root 4096 Jul 27 18:31 templates
|
||||||
|
#8 0.136 drwxrwxrwx 10 root root 4096 Jul 27 18:31 vendor
|
||||||
|
#8 0.136 -rwxrwxrwx 1 root root 9620 Jul 27 18:31 vendor.conf
|
||||||
|
#8 0.136 /bin/sh: stop: not found
|
||||||
|
```
|
||||||
327
docs/guides/bake/hcl-funcs.md
Normal file
327
docs/guides/bake/hcl-funcs.md
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
# User defined HCL functions
|
||||||
|
|
||||||
|
## Using interpolation to tag an image with the git sha
|
||||||
|
|
||||||
|
As shown in the [File definition](file-definition.md#variable) page, `bake`
|
||||||
|
supports variable blocks which are assigned to matching environment variables
|
||||||
|
or default values:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "TAG" {
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = ["webapp"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
tags = ["docker.io/username/webapp:${TAG}"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
alternatively, in json format:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"variable": {
|
||||||
|
"TAG": {
|
||||||
|
"default": "latest"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": ["webapp"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"tags": ["docker.io/username/webapp:${TAG}"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print webapp
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"docker.io/username/webapp:latest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ TAG=$(git rev-parse --short HEAD) docker buildx bake --print webapp
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"docker.io/username/webapp:985e9e9"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the `add` function
|
||||||
|
|
||||||
|
You can use [`go-cty` stdlib functions](https://github.com/zclconf/go-cty/tree/main/cty/function/stdlib).
|
||||||
|
Here we are using the `add` function.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "TAG" {
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = ["webapp"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
args = {
|
||||||
|
buildno = "${add(123, 1)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print webapp
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"buildno": "124"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Defining an `increment` function
|
||||||
|
|
||||||
|
It also supports [user defined functions](https://github.com/hashicorp/hcl/tree/main/ext/userfunc).
|
||||||
|
The following example defines a simple an `increment` function.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
function "increment" {
|
||||||
|
params = [number]
|
||||||
|
result = number + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = ["webapp"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
args = {
|
||||||
|
buildno = "${increment(123)}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print webapp
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"buildno": "124"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Only adding tags if a variable is not empty using an `notequal`
|
||||||
|
|
||||||
|
Here we are using the conditional `notequal` function which is just for
|
||||||
|
symmetry with the `equal` one.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "TAG" {default="" }
|
||||||
|
|
||||||
|
group "default" {
|
||||||
|
targets = [
|
||||||
|
"webapp",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
context="."
|
||||||
|
dockerfile="Dockerfile"
|
||||||
|
tags = [
|
||||||
|
"my-image:latest",
|
||||||
|
notequal("",TAG) ? "my-image:${TAG}": "",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print webapp
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"my-image:latest"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using variables in functions
|
||||||
|
|
||||||
|
You can refer variables to other variables like the target blocks can. Stdlib
|
||||||
|
functions can also be called but user functions can't at the moment.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "REPO" {
|
||||||
|
default = "user/repo"
|
||||||
|
}
|
||||||
|
|
||||||
|
function "tag" {
|
||||||
|
params = [tag]
|
||||||
|
result = ["${REPO}:${tag}"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp" {
|
||||||
|
tags = tag("v1")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print webapp
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"webapp"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"webapp": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"user/repo:v1"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using typed variables
|
||||||
|
|
||||||
|
Non-string variables are also accepted. The value passed with env is parsed
|
||||||
|
into suitable type first.
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.hcl
|
||||||
|
variable "FOO" {
|
||||||
|
default = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "IS_FOO" {
|
||||||
|
default = true
|
||||||
|
}
|
||||||
|
|
||||||
|
target "app" {
|
||||||
|
args = {
|
||||||
|
v1 = FOO > 5 ? "higher" : "lower"
|
||||||
|
v2 = IS_FOO ? "yes" : "no"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --print app
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"app"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"app": {
|
||||||
|
"context": ".",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"args": {
|
||||||
|
"v1": "lower",
|
||||||
|
"v2": "yes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
36
docs/guides/bake/index.md
Normal file
36
docs/guides/bake/index.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# High-level build options with Bake
|
||||||
|
|
||||||
|
> This command is experimental.
|
||||||
|
>
|
||||||
|
> The design of bake is in early stages, and we are looking for [feedback from users](https://github.com/docker/buildx/issues).
|
||||||
|
{: .experimental }
|
||||||
|
|
||||||
|
Buildx also aims to provide support for high-level build concepts that go beyond
|
||||||
|
invoking a single build command. We want to support building all the images in
|
||||||
|
your application together and let the users define project specific reusable
|
||||||
|
build flows that can then be easily invoked by anyone.
|
||||||
|
|
||||||
|
[BuildKit](https://github.com/moby/buildkit) efficiently handles multiple
|
||||||
|
concurrent build requests and de-duplicating work. The build commands can be
|
||||||
|
combined with general-purpose command runners (for example, `make`). However,
|
||||||
|
these tools generally invoke builds in sequence and therefore cannot leverage
|
||||||
|
the full potential of BuildKit parallelization, or combine BuildKit's output
|
||||||
|
for the user. For this use case, we have added a command called
|
||||||
|
[`docker buildx bake`](https://docs.docker.com/engine/reference/commandline/buildx_bake/).
|
||||||
|
|
||||||
|
The `bake` command supports building images from HCL, JSON and Compose files.
|
||||||
|
This is similar to [`docker compose build`](https://docs.docker.com/compose/reference/build/),
|
||||||
|
but allowing all the services to be built concurrently as part of a single
|
||||||
|
request. If multiple files are specified they are all read and configurations are
|
||||||
|
combined.
|
||||||
|
|
||||||
|
We recommend using HCL files as its experience is more aligned with buildx UX
|
||||||
|
and also allows better code reuse, different target groups and extended features.
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
* [File definition](file-definition.md)
|
||||||
|
* [Configuring builds](configuring-build.md)
|
||||||
|
* [User defined HCL functions](hcl-funcs.md)
|
||||||
|
* [Defining additional build contexts and linking targets](build-contexts.md)
|
||||||
|
* [Building from Compose file](compose-file.md)
|
||||||
48
docs/guides/cicd.md
Normal file
48
docs/guides/cicd.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# CI/CD
|
||||||
|
|
||||||
|
## GitHub Actions
|
||||||
|
|
||||||
|
Docker provides a [GitHub Action that will build and push your image](https://github.com/docker/build-push-action/#about)
|
||||||
|
using Buildx. Here is a simple workflow:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- 'main'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2
|
||||||
|
-
|
||||||
|
name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v2
|
||||||
|
-
|
||||||
|
name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
push: true
|
||||||
|
tags: user/app:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example we are also using 3 other actions:
|
||||||
|
|
||||||
|
* [`setup-buildx`](https://github.com/docker/setup-buildx-action) action will create and boot a builder using by
|
||||||
|
default the `docker-container` [builder driver](../reference/buildx_create.md#driver).
|
||||||
|
This is **not required but recommended** using it to be able to build multi-platform images, export cache, etc.
|
||||||
|
* [`setup-qemu`](https://github.com/docker/setup-qemu-action) action can be useful if you want
|
||||||
|
to add emulation support with QEMU to be able to build against more platforms.
|
||||||
|
* [`login`](https://github.com/docker/login-action) action will take care to log
|
||||||
|
in against a Docker registry.
|
||||||
23
docs/guides/cni-networking.md
Normal file
23
docs/guides/cni-networking.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# CNI networking
|
||||||
|
|
||||||
|
It can be useful to use a bridge network for your builder if for example you
|
||||||
|
encounter a network port contention during multiple builds. If you're using
|
||||||
|
the BuildKit image, CNI is not yet available in it, but you can create
|
||||||
|
[a custom BuildKit image with CNI support](https://github.com/moby/buildkit/blob/master/docs/cni-networking.md).
|
||||||
|
|
||||||
|
Now build this image:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --tag buildkit-cni:local --load .
|
||||||
|
```
|
||||||
|
|
||||||
|
Then [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/) that
|
||||||
|
will use this image:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create --use \
|
||||||
|
--name mybuilder \
|
||||||
|
--driver docker-container \
|
||||||
|
--driver-opt "image=buildkit-cni:local" \
|
||||||
|
--buildkitd-flags "--oci-worker-net=cni"
|
||||||
|
```
|
||||||
20
docs/guides/color-output.md
Normal file
20
docs/guides/color-output.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Color output controls
|
||||||
|
|
||||||
|
Buildx has support for modifying the colors that are used to output information
|
||||||
|
to the terminal. You can set the environment variable `BUILDKIT_COLORS` to
|
||||||
|
something like `run=123,20,245:error=yellow:cancel=blue:warning=white` to set
|
||||||
|
the colors that you would like to use:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Setting `NO_COLOR` to anything will disable any colorized output as recommended
|
||||||
|
by [no-color.org](https://no-color.org/):
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> Parsing errors will be reported but ignored. This will result in default
|
||||||
|
> color values being used where needed.
|
||||||
|
|
||||||
|
See also [the list of pre-defined colors](https://github.com/moby/buildkit/blob/master/util/progress/progressui/colors.go).
|
||||||
34
docs/guides/custom-network.md
Normal file
34
docs/guides/custom-network.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Using a custom network
|
||||||
|
|
||||||
|
[Create a network](https://docs.docker.com/engine/reference/commandline/network_create/)
|
||||||
|
named `foonet`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker network create foonet
|
||||||
|
```
|
||||||
|
|
||||||
|
[Create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
||||||
|
named `mybuilder` that will use this network:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create --use \
|
||||||
|
--name mybuilder \
|
||||||
|
--driver docker-container \
|
||||||
|
--driver-opt "network=foonet"
|
||||||
|
```
|
||||||
|
|
||||||
|
Boot and [inspect `mybuilder`](https://docs.docker.com/engine/reference/commandline/buildx_inspect/):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx inspect --bootstrap
|
||||||
|
```
|
||||||
|
|
||||||
|
[Inspect the builder container](https://docs.docker.com/engine/reference/commandline/inspect/)
|
||||||
|
and see what network is being used:
|
||||||
|
|
||||||
|
{% raw %}
|
||||||
|
```console
|
||||||
|
$ docker inspect buildx_buildkit_mybuilder0 --format={{.NetworkSettings.Networks}}
|
||||||
|
map[foonet:0xc00018c0c0]
|
||||||
|
```
|
||||||
|
{% endraw %}
|
||||||
63
docs/guides/custom-registry-config.md
Normal file
63
docs/guides/custom-registry-config.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# Using a custom registry configuration
|
||||||
|
|
||||||
|
If you [create a `docker-container` or `kubernetes` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/) and
|
||||||
|
have specified certificates for registries in the [BuildKit daemon configuration](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md),
|
||||||
|
the files will be copied into the container under `/etc/buildkit/certs` and
|
||||||
|
configuration will be updated to reflect that.
|
||||||
|
|
||||||
|
Take the following `buildkitd.toml` configuration that will be used for
|
||||||
|
pushing an image to this registry using self-signed certificates:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# /etc/buildkitd.toml
|
||||||
|
debug = true
|
||||||
|
[registry."myregistry.com"]
|
||||||
|
ca=["/etc/certs/myregistry.pem"]
|
||||||
|
[[registry."myregistry.com".keypair]]
|
||||||
|
key="/etc/certs/myregistry_key.pem"
|
||||||
|
cert="/etc/certs/myregistry_cert.pem"
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we have configured a self-signed certificate for `myregistry.com` registry.
|
||||||
|
|
||||||
|
Now [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
||||||
|
that will use this BuildKit configuration:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create --use \
|
||||||
|
--name mybuilder \
|
||||||
|
--driver docker-container \
|
||||||
|
--config /etc/buildkitd.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
Inspecting the builder container, you can see that buildkitd configuration
|
||||||
|
has changed:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker exec -it buildx_buildkit_mybuilder0 cat /etc/buildkit/buildkitd.toml
|
||||||
|
```
|
||||||
|
```toml
|
||||||
|
debug = true
|
||||||
|
|
||||||
|
[registry]
|
||||||
|
|
||||||
|
[registry."myregistry.com"]
|
||||||
|
ca = ["/etc/buildkit/certs/myregistry.com/myregistry.pem"]
|
||||||
|
|
||||||
|
[[registry."myregistry.com".keypair]]
|
||||||
|
cert = "/etc/buildkit/certs/myregistry.com/myregistry_cert.pem"
|
||||||
|
key = "/etc/buildkit/certs/myregistry.com/myregistry_key.pem"
|
||||||
|
```
|
||||||
|
|
||||||
|
And certificates copied inside the container:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker exec -it buildx_buildkit_mybuilder0 ls /etc/buildkit/certs/myregistry.com/
|
||||||
|
myregistry.pem myregistry_cert.pem myregistry_key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you should be able to push to the registry with this builder:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --push --tag myregistry.com/myimage:latest .
|
||||||
|
```
|
||||||
75
docs/guides/drivers/docker-container.md
Normal file
75
docs/guides/drivers/docker-container.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Docker container driver
|
||||||
|
|
||||||
|
The buildx docker-container driver allows creation of a managed and
|
||||||
|
customizable BuildKit environment inside a dedicated Docker container.
|
||||||
|
|
||||||
|
Using the docker-container driver has a couple of advantages over the basic
|
||||||
|
docker driver. Firstly, we can manually override the version of buildkit to
|
||||||
|
use, meaning that we can access the latest and greatest features as soon as
|
||||||
|
they're released, instead of waiting to upgrade to a newer version of Docker.
|
||||||
|
Additionally, we can access more complex features like multi-architecture
|
||||||
|
builds and the more advanced cache exporters, which are currently unsupported
|
||||||
|
in the default docker driver.
|
||||||
|
|
||||||
|
We can easily create a new builder that uses the docker-container driver:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create --name container --driver docker-container
|
||||||
|
container
|
||||||
|
```
|
||||||
|
|
||||||
|
We should then be able to see it on our list of available builders:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx ls
|
||||||
|
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
||||||
|
container docker-container
|
||||||
|
container0 desktop-linux inactive
|
||||||
|
default docker
|
||||||
|
default default running 20.10.17 linux/amd64, linux/386
|
||||||
|
```
|
||||||
|
|
||||||
|
If we trigger a build, the appropriate `moby/buildkit` image will be pulled
|
||||||
|
from [Docker Hub](https://hub.docker.com/u/moby/buildkit), the image started,
|
||||||
|
and our build submitted to our containerized build server.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build -t <image> --builder=container .
|
||||||
|
WARNING: No output specified with docker-container driver. Build result will only remain in the build cache. To push result image into registry use --push or to load image into docker use --load
|
||||||
|
#1 [internal] booting buildkit
|
||||||
|
#1 pulling image moby/buildkit:buildx-stable-1
|
||||||
|
#1 pulling image moby/buildkit:buildx-stable-1 1.9s done
|
||||||
|
#1 creating container buildx_buildkit_container0
|
||||||
|
#1 creating container buildx_buildkit_container0 0.5s done
|
||||||
|
#1 DONE 2.4s
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the warning "Build result will only remain in the build cache" - unlike
|
||||||
|
the `docker` driver, the built image must be explicitly loaded into the local
|
||||||
|
image store. We can use the `--load` flag for this:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --load -t <image> --builder=container .
|
||||||
|
...
|
||||||
|
=> exporting to oci image format 7.7s
|
||||||
|
=> => exporting layers 4.9s
|
||||||
|
=> => exporting manifest sha256:4e4ca161fa338be2c303445411900ebbc5fc086153a0b846ac12996960b479d3 0.0s
|
||||||
|
=> => exporting config sha256:adf3eec768a14b6e183a1010cb96d91155a82fd722a1091440c88f3747f1f53f 0.0s
|
||||||
|
=> => sending tarball 2.8s
|
||||||
|
=> importing to docker
|
||||||
|
```
|
||||||
|
|
||||||
|
The image should then be available in the image store:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker image ls
|
||||||
|
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||||
|
<image> latest adf3eec768a1 2 minutes ago 197MB
|
||||||
|
```
|
||||||
|
|
||||||
|
## Further reading
|
||||||
|
|
||||||
|
For more information on the docker-container driver, see the [buildx reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver).
|
||||||
|
|
||||||
|
<!--- FIXME: for 0.9, make reference link relative --->
|
||||||
50
docs/guides/drivers/docker.md
Normal file
50
docs/guides/drivers/docker.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Docker driver
|
||||||
|
|
||||||
|
The buildx docker driver is the default builtin driver, that uses the BuildKit
|
||||||
|
server components built directly into the docker engine.
|
||||||
|
|
||||||
|
No setup should be required for the docker driver - it should already be
|
||||||
|
configured for you:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx ls
|
||||||
|
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
||||||
|
default docker
|
||||||
|
default default running 20.10.17 linux/amd64, linux/386
|
||||||
|
```
|
||||||
|
|
||||||
|
This builder is ready to build with out-of-the-box, requiring no extra setup,
|
||||||
|
so you can get going with a `docker buildx build` as soon as you like.
|
||||||
|
|
||||||
|
Depending on your personal setup, you may find multiple builders in your list
|
||||||
|
the use the docker driver. For example, on a system that runs both a package
|
||||||
|
managed version of dockerd, as well as Docker Desktop, you might have the
|
||||||
|
following:
|
||||||
|
|
||||||
|
```console
|
||||||
|
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
||||||
|
default docker
|
||||||
|
default default running 20.10.17 linux/amd64, linux/386
|
||||||
|
desktop-linux * docker
|
||||||
|
desktop-linux desktop-linux running 20.10.17 linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6
|
||||||
|
```
|
||||||
|
|
||||||
|
This is because the docker driver builders are automatically pulled from
|
||||||
|
the available [Docker Contexts](https://docs.docker.com/engine/context/working-with-contexts/).
|
||||||
|
When you add new contexts using `docker context create`, these will appear in
|
||||||
|
your list of buildx builders.
|
||||||
|
|
||||||
|
Unlike the [other drivers](../index.md), builders using the docker driver
|
||||||
|
cannot be manually created, and can only be automatically created from the
|
||||||
|
docker context. Additionally, they cannot be configured to a specific BuildKit
|
||||||
|
version, and cannot take any extra parameters, as these are both preset by the
|
||||||
|
Docker engine internally.
|
||||||
|
|
||||||
|
If you want the extra configuration and flexibility without too much more
|
||||||
|
overhead, then see the help page for the [docker-container driver](./docker-container.md).
|
||||||
|
|
||||||
|
## Further reading
|
||||||
|
|
||||||
|
For more information on the docker driver, see the [buildx reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver).
|
||||||
|
|
||||||
|
<!--- FIXME: for 0.9, make reference link relative --->
|
||||||
41
docs/guides/drivers/index.md
Normal file
41
docs/guides/drivers/index.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Buildx drivers overview
|
||||||
|
|
||||||
|
The buildx client connects out to the BuildKit backend to execute builds -
|
||||||
|
Buildx drivers allow fine-grained control over management of the backend, and
|
||||||
|
supports several different options for where and how BuildKit should run.
|
||||||
|
|
||||||
|
Currently, we support the following drivers:
|
||||||
|
|
||||||
|
- The `docker` driver, that uses the BuildKit library bundled into the Docker
|
||||||
|
daemon.
|
||||||
|
([guide](./docker.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
||||||
|
- The `docker-container` driver, that launches a dedicated BuildKit container
|
||||||
|
using Docker, for access to advanced features.
|
||||||
|
([guide](./docker-container.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
||||||
|
- The `kubernetes` driver, that launches dedicated BuildKit pods in a
|
||||||
|
remote Kubernetes cluster, for scalable builds.
|
||||||
|
([guide](./kubernetes.md), [reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver))
|
||||||
|
- The `remote` driver, that allows directly connecting to a manually managed
|
||||||
|
BuildKit daemon, for more custom setups.
|
||||||
|
([guide](./remote.md))
|
||||||
|
|
||||||
|
<!--- FIXME: for 0.9, make links relative, and add reference link for remote --->
|
||||||
|
|
||||||
|
To create a new builder that uses one of the above drivers, you can use the
|
||||||
|
[`docker buildx create`](https://docs.docker.com/engine/reference/commandline/buildx_create/) command:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create --name=<builder-name> --driver=<driver> --driver-opt=<driver-options>
|
||||||
|
```
|
||||||
|
|
||||||
|
The build experience is very similar across drivers, however, there are some
|
||||||
|
features that are not evenly supported across the board, notably, the `docker`
|
||||||
|
driver does not include support for certain output/caching types.
|
||||||
|
|
||||||
|
| Feature | `docker` | `docker-container` | `kubernetes` | `remote` |
|
||||||
|
| :---------------------------- | :-------------: | :----------------: | :----------: | :--------------------: |
|
||||||
|
| **Automatic `--load`** | ✅ | ❌ | ❌ | ❌ |
|
||||||
|
| **Cache export** | ❔ (inline only) | ✅ | ✅ | ✅ |
|
||||||
|
| **Docker/OCI tarball output** | ❌ | ✅ | ✅ | ✅ |
|
||||||
|
| **Multi-arch images** | ❌ | ✅ | ✅ | ✅ |
|
||||||
|
| **BuildKit configuration** | ❌ | ✅ | ✅ | ❔ (managed externally) |
|
||||||
238
docs/guides/drivers/kubernetes.md
Normal file
238
docs/guides/drivers/kubernetes.md
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
# Kubernetes driver
|
||||||
|
|
||||||
|
The buildx kubernetes driver allows connecting your local development or ci
|
||||||
|
environments to your kubernetes cluster to allow access to more powerful
|
||||||
|
and varied compute resources.
|
||||||
|
|
||||||
|
This guide assumes you already have an existing kubernetes cluster - if you don't already
|
||||||
|
have one, you can easily follow along by installing
|
||||||
|
[minikube](https://minikube.sigs.k8s.io/docs/).
|
||||||
|
|
||||||
|
Before connecting buildx to your cluster, you may want to create a dedicated
|
||||||
|
namespace using `kubectl` to keep your buildx-managed resources separate. You
|
||||||
|
can call your namespace anything you want, or use the existing `default`
|
||||||
|
namespace, but we'll create a `buildkit` namespace for now:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ kubectl create namespace buildkit
|
||||||
|
```
|
||||||
|
|
||||||
|
Then create a new buildx builder:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create \
|
||||||
|
--bootstrap \
|
||||||
|
--name=kube \
|
||||||
|
--driver=kubernetes \
|
||||||
|
--driver-opt=namespace=buildkit
|
||||||
|
```
|
||||||
|
|
||||||
|
This assumes that the kubernetes cluster you want to connect to is currently
|
||||||
|
accessible via the kubectl command, with the `KUBECONFIG` environment variable
|
||||||
|
[set appropriately](https://kubernetes.io/docs/tasks/access-application-cluster/configure-access-multiple-clusters/#set-the-kubeconfig-environment-variable)
|
||||||
|
if neccessary.
|
||||||
|
|
||||||
|
You should now be able to see the builder in the list of buildx builders:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx ls
|
||||||
|
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
|
||||||
|
kube kubernetes
|
||||||
|
kube0-6977cdcb75-k9h9m running linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
|
||||||
|
default * docker
|
||||||
|
default default running linux/amd64, linux/386
|
||||||
|
```
|
||||||
|
|
||||||
|
The buildx driver creates the neccessary resources on your cluster in the
|
||||||
|
specified namespace (in this case, `buildkit`), while keeping your
|
||||||
|
driver configuration locally. You can see the running pods with:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ kubectl -n buildkit get deployments
|
||||||
|
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||||
|
kube0 1/1 1 1 32s
|
||||||
|
|
||||||
|
$ kubectl -n buildkit get pods
|
||||||
|
NAME READY STATUS RESTARTS AGE
|
||||||
|
kube0-6977cdcb75-k9h9m 1/1 Running 0 32s
|
||||||
|
```
|
||||||
|
|
||||||
|
You can use your new builder by including the `--builder` flag when running
|
||||||
|
buildx commands. For example (replacing `<user>` and `<image>` with your Docker
|
||||||
|
Hub username and desired image output respectively):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build . \
|
||||||
|
--builder=kube \
|
||||||
|
-t <user>/<image> \
|
||||||
|
--push
|
||||||
|
```
|
||||||
|
|
||||||
|
## Scaling Buildkit
|
||||||
|
|
||||||
|
One of the main advantages of the kubernetes builder is that you can easily
|
||||||
|
scale your builder up and down to handle increased build load. These controls
|
||||||
|
are exposed via the following options:
|
||||||
|
|
||||||
|
- `replicas=N`
|
||||||
|
- This scales the number of buildkit pods to the desired size. By default,
|
||||||
|
only a single pod will be created, but increasing this allows taking of
|
||||||
|
advantage of multiple nodes in your cluster.
|
||||||
|
- `requests.cpu`, `requests.memory`, `limits.cpu`, `limits.memory`
|
||||||
|
- These options allow requesting and limiting the resources available to each
|
||||||
|
buildkit pod according to the official kubernetes documentation
|
||||||
|
[here](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/).
|
||||||
|
|
||||||
|
For example, to create 4 replica buildkit pods:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create \
|
||||||
|
--bootstrap \
|
||||||
|
--name=kube \
|
||||||
|
--driver=kubernetes \
|
||||||
|
--driver-opt=namespace=buildkit,replicas=4
|
||||||
|
```
|
||||||
|
|
||||||
|
Listing the pods, we get:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ kubectl -n buildkit get deployments
|
||||||
|
NAME READY UP-TO-DATE AVAILABLE AGE
|
||||||
|
kube0 4/4 4 4 8s
|
||||||
|
|
||||||
|
$ kubectl -n buildkit get pods
|
||||||
|
NAME READY STATUS RESTARTS AGE
|
||||||
|
kube0-6977cdcb75-48ld2 1/1 Running 0 8s
|
||||||
|
kube0-6977cdcb75-rkc6b 1/1 Running 0 8s
|
||||||
|
kube0-6977cdcb75-vb4ks 1/1 Running 0 8s
|
||||||
|
kube0-6977cdcb75-z4fzs 1/1 Running 0 8s
|
||||||
|
```
|
||||||
|
|
||||||
|
Additionally, you can use the `loadbalance=(sticky|random)` option to control
|
||||||
|
the load-balancing behavior when there are multiple replicas. While `random`
|
||||||
|
should selects random nodes from the available pool, which should provide
|
||||||
|
better balancing across all replicas, `sticky` (the default) attempts to
|
||||||
|
connect the same build performed multiple times to the same node each time,
|
||||||
|
ensuring better local cache utilization.
|
||||||
|
|
||||||
|
For more information on scalability, see the options for [buildx create](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver-opt).
|
||||||
|
|
||||||
|
## Multi-platform builds
|
||||||
|
|
||||||
|
The kubernetes buildx driver has support for creating [multi-platform images](https://docs.docker.com/build/buildx/multiplatform-images/),
|
||||||
|
for easily building for multiple platforms at once.
|
||||||
|
|
||||||
|
### QEMU
|
||||||
|
|
||||||
|
Like the other containerized driver `docker-container`, the kubernetes driver
|
||||||
|
also supports using [QEMU](https://www.qemu.org/) (user mode) to build
|
||||||
|
non-native platforms. If using a default setup like above, no extra setup
|
||||||
|
should be needed, you should just be able to start building for other
|
||||||
|
architectures, by including the `--platform` flag.
|
||||||
|
|
||||||
|
For example, to build a Linux image for `amd64` and `arm64`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build . \
|
||||||
|
--builder=kube \
|
||||||
|
--platform=linux/amd64,linux/arm64 \
|
||||||
|
-t <user>/<image> \
|
||||||
|
--push
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Warning**
|
||||||
|
> QEMU performs full-system emulation of non-native platforms, which is *much*
|
||||||
|
> slower than native builds. Compute-heavy tasks like compilation and
|
||||||
|
> compression/decompression will likely take a large performance hit.
|
||||||
|
|
||||||
|
Note, if you're using a custom buildkit image using the `image=<image>` driver
|
||||||
|
option, or invoking non-native binaries from within your build, you may need to
|
||||||
|
explicitly enable QEMU using the `qemu.install` option during driver creation:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create \
|
||||||
|
--bootstrap \
|
||||||
|
--name=kube \
|
||||||
|
--driver=kubernetes \
|
||||||
|
--driver-opt=namespace=buildkit,qemu.install=true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Native
|
||||||
|
|
||||||
|
If you have access to cluster nodes of different architectures, we can
|
||||||
|
configure the kubernetes driver to take advantage of these for native builds.
|
||||||
|
To do this, we need to use the `--append` feature of `docker buildx create`.
|
||||||
|
|
||||||
|
To start, we can create our builder with explicit support for a single
|
||||||
|
architecture, `amd64`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create \
|
||||||
|
--bootstrap \
|
||||||
|
--name=kube \
|
||||||
|
--driver=kubernetes \
|
||||||
|
--platform=linux/amd64 \
|
||||||
|
--node=builder-amd64 \
|
||||||
|
--driver-opt=namespace=buildkit,nodeselector="kubernetes.io/arch=amd64"
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a buildx builder `kube` containing a single builder node `builder-amd64`.
|
||||||
|
Note that the buildx concept of a node is not the same as the kubernetes
|
||||||
|
concept of a node - the buildx node in this case could connect multiple
|
||||||
|
kubernetes nodes of the same architecture together.
|
||||||
|
|
||||||
|
With our `kube` driver created, we can now introduce another architecture into
|
||||||
|
the mix, for example, like before we can use `arm64`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create \
|
||||||
|
--append \
|
||||||
|
--bootstrap \
|
||||||
|
--name=kube \
|
||||||
|
--driver=kubernetes \
|
||||||
|
--platform=linux/arm64 \
|
||||||
|
--node=builder-arm64 \
|
||||||
|
--driver-opt=namespace=buildkit,nodeselector="kubernetes.io/arch=arm64"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you list builders now, you should be able to see both nodes present:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx ls
|
||||||
|
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
|
||||||
|
kube kubernetes
|
||||||
|
builder-amd64 kubernetes:///kube?deployment=builder-amd64&kubeconfig= running linux/amd64*, linux/amd64/v2, linux/amd64/v3, linux/386
|
||||||
|
builder-arm64 kubernetes:///kube?deployment=builder-arm64&kubeconfig= running linux/arm64*
|
||||||
|
```
|
||||||
|
|
||||||
|
You should now be able to build multi-arch images with `amd64` and `arm64`
|
||||||
|
combined, by specifying those platforms together in your buildx command:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --builder=kube --platform=linux/amd64,linux/arm64 -t <user>/<image> --push .
|
||||||
|
```
|
||||||
|
|
||||||
|
You can repeat the `buildx create --append` command for as many different
|
||||||
|
architectures that you want to support.
|
||||||
|
|
||||||
|
## Rootless mode
|
||||||
|
|
||||||
|
The kubernetes driver supports rootless mode. For more information on how
|
||||||
|
rootless mode works, and it's requirements, see [here](https://github.com/moby/buildkit/blob/master/docs/rootless.md).
|
||||||
|
|
||||||
|
To enable it in your cluster, you can use the `rootless=true` driver option:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create \
|
||||||
|
--name=kube \
|
||||||
|
--driver=kubernetes \
|
||||||
|
--driver-opt=namespace=buildkit,rootless=true
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create your pods without `securityContext.privileged`.
|
||||||
|
|
||||||
|
## Further reading
|
||||||
|
|
||||||
|
For more information on the kubernetes driver, see the [buildx reference](https://docs.docker.com/engine/reference/commandline/buildx_create/#driver).
|
||||||
|
|
||||||
|
<!--- FIXME: for 0.9, make reference link relative --->
|
||||||
178
docs/guides/drivers/remote.md
Normal file
178
docs/guides/drivers/remote.md
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# Remote driver
|
||||||
|
|
||||||
|
The buildx remote driver allows for more complex custom build workloads that
|
||||||
|
allow users to connect to external buildkit instances. This is useful for
|
||||||
|
scenarios that require manual management of the buildkit daemon, or where a
|
||||||
|
buildkit daemon is exposed from another source.
|
||||||
|
|
||||||
|
To connect to a running buildkitd instance:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create \
|
||||||
|
--name remote \
|
||||||
|
--driver remote \
|
||||||
|
tcp://localhost:1234
|
||||||
|
```
|
||||||
|
|
||||||
|
## Remote Buildkit over Unix sockets
|
||||||
|
|
||||||
|
In this scenario, we'll create a setup with buildkitd listening on a unix
|
||||||
|
socket, and have buildx connect through it.
|
||||||
|
|
||||||
|
Firstly, ensure that [buildkit](https://github.com/moby/buildkit) is installed.
|
||||||
|
For example, you can launch an instance of buildkitd with:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ sudo ./buildkitd --group $(id -gn) --addr unix://$HOME/buildkitd.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, [see here](https://github.com/moby/buildkit/blob/master/docs/rootless.md)
|
||||||
|
for running buildkitd in rootless mode or [here](https://github.com/moby/buildkit/tree/master/examples/systemd)
|
||||||
|
for examples of running it as a systemd service.
|
||||||
|
|
||||||
|
You should now have a unix socket accessible to your user, that is available to
|
||||||
|
connect to:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ ls -lh /home/user/buildkitd.sock
|
||||||
|
srw-rw---- 1 root user 0 May 5 11:04 /home/user/buildkitd.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
You can then connect buildx to it with the remote driver:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create \
|
||||||
|
--name remote-unix \
|
||||||
|
--driver remote \
|
||||||
|
unix://$HOME/buildkitd.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
If you list available builders, you should then see `remote-unix` among them:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx ls
|
||||||
|
NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
|
||||||
|
remote-unix remote
|
||||||
|
remote-unix0 unix:///home/.../buildkitd.sock running linux/amd64, linux/amd64/v2, linux/amd64/v3, linux/386
|
||||||
|
default * docker
|
||||||
|
default default running linux/amd64, linux/386
|
||||||
|
```
|
||||||
|
|
||||||
|
We can switch to this new builder as the default using `docker buildx use remote-unix`,
|
||||||
|
or specify it per build:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --builder=remote-unix -t test --load .
|
||||||
|
```
|
||||||
|
|
||||||
|
(remember that `--load` is necessary when not using the default `docker`
|
||||||
|
driver, to load the build result into the docker daemon)
|
||||||
|
|
||||||
|
## Remote Buildkit in Docker container
|
||||||
|
|
||||||
|
In this scenario, we'll create a similar setup to the `docker-container`
|
||||||
|
driver, by manually booting a buildkit docker container and connecting to it
|
||||||
|
using the buildx remote driver. In most cases you'd probably just use the
|
||||||
|
`docker-container` driver that connects to buildkit through the Docker daemon,
|
||||||
|
but in this case we manually create a container and access it via it's exposed
|
||||||
|
port.
|
||||||
|
|
||||||
|
First, we need to generate certificates for buildkit - you can use the
|
||||||
|
[create-certs.sh](https://github.com/moby/buildkit/v0.10.3/master/examples/kubernetes/create-certs.sh)
|
||||||
|
script as a starting point. Note, that while it is *possible* to expose
|
||||||
|
buildkit over TCP without using TLS, it is **not recommended**, since this will
|
||||||
|
allow arbitrary access to buildkit without credentials.
|
||||||
|
|
||||||
|
With our certificates generated in `.certs/`, we startup the container:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker run -d --rm \
|
||||||
|
--name=remote-buildkitd \
|
||||||
|
--privileged \
|
||||||
|
-p 1234:1234 \
|
||||||
|
-v $PWD/.certs:/etc/buildkit/certs \
|
||||||
|
moby/buildkit:latest \
|
||||||
|
--addr tcp://0.0.0.0:1234 \
|
||||||
|
--tlscacert /etc/buildkit/certs/ca.pem \
|
||||||
|
--tlscert /etc/buildkit/certs/daemon-cert.pem \
|
||||||
|
--tlskey /etc/buildkit/certs/daemon-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
The above command starts a buildkit container and exposes the daemon's port
|
||||||
|
1234 to localhost.
|
||||||
|
|
||||||
|
We can now connect to this running container using buildx:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create \
|
||||||
|
--name remote-container \
|
||||||
|
--driver remote \
|
||||||
|
--driver-opt cacert=.certs/ca.pem,cert=.certs/client-cert.pem,key=.certs/client-key.pem,servername=... \
|
||||||
|
tcp://localhost:1234
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, we could use the `docker-container://` URL scheme to connect
|
||||||
|
to the buildkit container without specifying a port:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create \
|
||||||
|
--name remote-container \
|
||||||
|
--driver remote \
|
||||||
|
docker-container://remote-container
|
||||||
|
```
|
||||||
|
|
||||||
|
## Remote Buildkit in Kubernetes
|
||||||
|
|
||||||
|
In this scenario, we'll create a similar setup to the `kubernetes` driver by
|
||||||
|
manually creating a buildkit `Deployment`. While the `kubernetes` driver will
|
||||||
|
do this under-the-hood, it might sometimes be desirable to scale buildkit
|
||||||
|
manually. Additionally, when executing builds from inside Kubernetes pods,
|
||||||
|
the buildx builder will need to be recreated from within each pod or copied
|
||||||
|
between them.
|
||||||
|
|
||||||
|
Firstly, we can create a kubernetes deployment of buildkitd, as per the
|
||||||
|
instructions [here](https://github.com/moby/buildkit/tree/master/examples/kubernetes).
|
||||||
|
Following the guide, we setup certificates for the buildkit daemon and client
|
||||||
|
(as above using [create-certs.sh](https://github.com/moby/buildkit/blob/v0.10.3/examples/kubernetes/create-certs.sh))
|
||||||
|
and create a `Deployment` of buildkit pods with a service that connects to
|
||||||
|
them.
|
||||||
|
|
||||||
|
Assuming that the service is called `buildkitd`, we can create a remote builder
|
||||||
|
in buildx, ensuring that the listed certificate files are present:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create \
|
||||||
|
--name remote-kubernetes \
|
||||||
|
--driver remote \
|
||||||
|
--driver-opt cacert=.certs/ca.pem,cert=.certs/client-cert.pem,key=.certs/client-key.pem \
|
||||||
|
tcp://buildkitd.default.svc:1234
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that the above will only work in-cluster (since the buildkit setup guide
|
||||||
|
only creates a ClusterIP service). To configure the builder to be accessible
|
||||||
|
remotely, you can use an appropriately configured Ingress, which is outside the
|
||||||
|
scope of this guide.
|
||||||
|
|
||||||
|
To access the service remotely, we can use the port forwarding mechanism in
|
||||||
|
kubectl:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ kubectl port-forward svc/buildkitd 1234:1234
|
||||||
|
```
|
||||||
|
|
||||||
|
Then you can simply point the remote driver at `tcp://localhost:1234`.
|
||||||
|
|
||||||
|
Alternatively, we could use the `kube-pod://` URL scheme to connect
|
||||||
|
directly to a buildkit pod through the kubernetes api (note that this method
|
||||||
|
will only connect to a single pod in the deployment):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ kubectl get pods --selector=app=buildkitd -o json | jq -r '.items[].metadata.name
|
||||||
|
buildkitd-XXXXXXXXXX-xxxxx
|
||||||
|
$ docker buildx create \
|
||||||
|
--name remote-container \
|
||||||
|
--driver remote \
|
||||||
|
kube-pod://buildkitd-XXXXXXXXXX-xxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
<!--- FIXME: for 0.9, add further reading section with link to reference --->
|
||||||
31
docs/guides/opentelemetry.md
Normal file
31
docs/guides/opentelemetry.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# OpenTelemetry support
|
||||||
|
|
||||||
|
To capture the trace to [Jaeger](https://github.com/jaegertracing/jaeger), set
|
||||||
|
`JAEGER_TRACE` environment variable to the collection address using a `driver-opt`.
|
||||||
|
|
||||||
|
First create a Jaeger container:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker run -d --name jaeger -p "6831:6831/udp" -p "16686:16686" jaegertracing/all-in-one
|
||||||
|
```
|
||||||
|
|
||||||
|
Then [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
||||||
|
that will use the Jaeger instance via the `JAEGER_TRACE` env var:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create --use \
|
||||||
|
--name mybuilder \
|
||||||
|
--driver docker-container \
|
||||||
|
--driver-opt "network=host" \
|
||||||
|
--driver-opt "env.JAEGER_TRACE=localhost:6831"
|
||||||
|
```
|
||||||
|
|
||||||
|
Boot and [inspect `mybuilder`](https://docs.docker.com/engine/reference/commandline/buildx_inspect/):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx inspect --bootstrap
|
||||||
|
```
|
||||||
|
|
||||||
|
Buildx commands should be traced at `http://127.0.0.1:16686/`:
|
||||||
|
|
||||||
|

|
||||||
62
docs/guides/registry-mirror.md
Normal file
62
docs/guides/registry-mirror.md
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
# Registry mirror
|
||||||
|
|
||||||
|
You can define a registry mirror to use for your builds by providing a [BuildKit daemon configuration](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md)
|
||||||
|
while creating a builder with the [`--config` flags](https://docs.docker.com/engine/reference/commandline/buildx_create/#config).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# /etc/buildkitd.toml
|
||||||
|
debug = true
|
||||||
|
[registry."docker.io"]
|
||||||
|
mirrors = ["mirror.gcr.io"]
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> `debug = true` has been added to be able to debug requests
|
||||||
|
> in the BuildKit daemon and see if the mirror is effectively used.
|
||||||
|
|
||||||
|
Then [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
||||||
|
that will use this BuildKit configuration:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create --use \
|
||||||
|
--name mybuilder \
|
||||||
|
--driver docker-container \
|
||||||
|
--config /etc/buildkitd.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
Boot and [inspect `mybuilder`](https://docs.docker.com/engine/reference/commandline/buildx_inspect/):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx inspect --bootstrap
|
||||||
|
```
|
||||||
|
|
||||||
|
Build an image:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --load . -f-<<EOF
|
||||||
|
FROM alpine
|
||||||
|
RUN echo "hello world"
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
Now let's check the BuildKit logs in the builder container:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker logs buildx_buildkit_mybuilder0
|
||||||
|
```
|
||||||
|
```text
|
||||||
|
...
|
||||||
|
time="2022-02-06T17:47:48Z" level=debug msg="do request" request.header.accept="application/vnd.docker.container.image.v1+json, */*" request.header.user-agent=containerd/1.5.8+unknown request.method=GET spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||||
|
time="2022-02-06T17:47:48Z" level=debug msg="fetch response received" response.header.accept-ranges=bytes response.header.age=1356 response.header.alt-svc="h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"" response.header.cache-control="public, max-age=3600" response.header.content-length=1469 response.header.content-type=application/octet-stream response.header.date="Sun, 06 Feb 2022 17:25:17 GMT" response.header.etag="\"774380abda8f4eae9a149e5d5d3efc83\"" response.header.expires="Sun, 06 Feb 2022 18:25:17 GMT" response.header.last-modified="Wed, 24 Nov 2021 21:07:57 GMT" response.header.server=UploadServer response.header.x-goog-generation=1637788077652182 response.header.x-goog-hash="crc32c=V3DSrg==" response.header.x-goog-hash.1="md5=d0OAq9qPTq6aFJ5dXT78gw==" response.header.x-goog-metageneration=1 response.header.x-goog-storage-class=STANDARD response.header.x-goog-stored-content-encoding=identity response.header.x-goog-stored-content-length=1469 response.header.x-guploader-uploadid=ADPycduqQipVAXc3tzXmTzKQ2gTT6CV736B2J628smtD1iDytEyiYCgvvdD8zz9BT1J1sASUq9pW_ctUyC4B-v2jvhIxnZTlKg response.status="200 OK" spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||||
|
time="2022-02-06T17:47:48Z" level=debug msg="fetch response received" response.header.accept-ranges=bytes response.header.age=760 response.header.alt-svc="h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"" response.header.cache-control="public, max-age=3600" response.header.content-length=1471 response.header.content-type=application/octet-stream response.header.date="Sun, 06 Feb 2022 17:35:13 GMT" response.header.etag="\"35d688bd15327daafcdb4d4395e616a8\"" response.header.expires="Sun, 06 Feb 2022 18:35:13 GMT" response.header.last-modified="Wed, 24 Nov 2021 21:07:12 GMT" response.header.server=UploadServer response.header.x-goog-generation=1637788032100793 response.header.x-goog-hash="crc32c=aWgRjA==" response.header.x-goog-hash.1="md5=NdaIvRUyfar8201DleYWqA==" response.header.x-goog-metageneration=1 response.header.x-goog-storage-class=STANDARD response.header.x-goog-stored-content-encoding=identity response.header.x-goog-stored-content-length=1471 response.header.x-guploader-uploadid=ADPycdtR-gJYwC7yHquIkJWFFG8FovDySvtmRnZBqlO3yVDanBXh_VqKYt400yhuf0XbQ3ZMB9IZV2vlcyHezn_Pu3a1SMMtiw response.status="200 OK" spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||||
|
time="2022-02-06T17:47:48Z" level=debug msg=fetch spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||||
|
time="2022-02-06T17:47:48Z" level=debug msg=fetch spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||||
|
time="2022-02-06T17:47:48Z" level=debug msg=fetch spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||||
|
time="2022-02-06T17:47:48Z" level=debug msg=fetch spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||||
|
time="2022-02-06T17:47:48Z" level=debug msg="do request" request.header.accept="application/vnd.docker.image.rootfs.diff.tar.gzip, */*" request.header.user-agent=containerd/1.5.8+unknown request.method=GET spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||||
|
time="2022-02-06T17:47:48Z" level=debug msg="fetch response received" response.header.accept-ranges=bytes response.header.age=1356 response.header.alt-svc="h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"" response.header.cache-control="public, max-age=3600" response.header.content-length=2818413 response.header.content-type=application/octet-stream response.header.date="Sun, 06 Feb 2022 17:25:17 GMT" response.header.etag="\"1d55e7be5a77c4a908ad11bc33ebea1c\"" response.header.expires="Sun, 06 Feb 2022 18:25:17 GMT" response.header.last-modified="Wed, 24 Nov 2021 21:07:06 GMT" response.header.server=UploadServer response.header.x-goog-generation=1637788026431708 response.header.x-goog-hash="crc32c=ZojF+g==" response.header.x-goog-hash.1="md5=HVXnvlp3xKkIrRG8M+vqHA==" response.header.x-goog-metageneration=1 response.header.x-goog-storage-class=STANDARD response.header.x-goog-stored-content-encoding=identity response.header.x-goog-stored-content-length=2818413 response.header.x-guploader-uploadid=ADPycdsebqxiTBJqZ0bv9zBigjFxgQydD2ESZSkKchpE0ILlN9Ibko3C5r4fJTJ4UR9ddp-UBd-2v_4eRpZ8Yo2llW_j4k8WhQ response.status="200 OK" spanID=9460e5b6e64cec91 traceID=b162d3040ddf86d6614e79c66a01a577
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
As you can see, requests come from the GCR registry mirror (`response.header.x-goog*`).
|
||||||
33
docs/guides/resource-limiting.md
Normal file
33
docs/guides/resource-limiting.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Resource limiting
|
||||||
|
|
||||||
|
## Max parallelism
|
||||||
|
|
||||||
|
You can limit the parallelism of the BuildKit solver, which is particularly useful
|
||||||
|
for low-powered machines, using a [BuildKit daemon configuration](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md)
|
||||||
|
while creating a builder with the [`--config` flags](https://docs.docker.com/engine/reference/commandline/buildx_create/#config).
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# /etc/buildkitd.toml
|
||||||
|
[worker.oci]
|
||||||
|
max-parallelism = 4
|
||||||
|
```
|
||||||
|
|
||||||
|
Now you can [create a `docker-container` builder](https://docs.docker.com/engine/reference/commandline/buildx_create/)
|
||||||
|
that will use this BuildKit configuration to limit parallelism.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create --use \
|
||||||
|
--name mybuilder \
|
||||||
|
--driver docker-container \
|
||||||
|
--config /etc/buildkitd.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limit on TCP connections
|
||||||
|
|
||||||
|
We are also now limiting TCP connections to **4 per registry** with an additional
|
||||||
|
connection not used for layer pulls and pushes. This limitation will be able to
|
||||||
|
manage TCP connection per host to avoid your build being stuck while pulling
|
||||||
|
images. The additional connection is used for metadata requests
|
||||||
|
(image config retrieval) to enhance the overall build time.
|
||||||
|
|
||||||
|
More info: [moby/buildkit#2259](https://github.com/moby/buildkit/pull/2259)
|
||||||
43
docs/reference/buildx.md
Normal file
43
docs/reference/buildx.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# buildx
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx [OPTIONS] COMMAND
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Extended build capabilities with BuildKit
|
||||||
|
|
||||||
|
### Subcommands
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| [`bake`](buildx_bake.md) | Build from a file |
|
||||||
|
| [`build`](buildx_build.md) | Start a build |
|
||||||
|
| [`create`](buildx_create.md) | Create a new builder instance |
|
||||||
|
| [`du`](buildx_du.md) | Disk usage |
|
||||||
|
| [`imagetools`](buildx_imagetools.md) | Commands to work on images in registry |
|
||||||
|
| [`inspect`](buildx_inspect.md) | Inspect current builder instance |
|
||||||
|
| [`install`](buildx_install.md) | Install buildx as a 'docker builder' alias |
|
||||||
|
| [`ls`](buildx_ls.md) | List builder instances |
|
||||||
|
| [`prune`](buildx_prune.md) | Remove build cache |
|
||||||
|
| [`rm`](buildx_rm.md) | Remove a builder instance |
|
||||||
|
| [`stop`](buildx_stop.md) | Stop builder instance |
|
||||||
|
| [`uninstall`](buildx_uninstall.md) | Uninstall the 'docker builder' alias |
|
||||||
|
| [`use`](buildx_use.md) | Set the current builder instance |
|
||||||
|
| [`version`](buildx_version.md) | Show buildx version information |
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
You can also use the `BUILDX_BUILDER` environment variable.
|
||||||
164
docs/reference/buildx_bake.md
Normal file
164
docs/reference/buildx_bake.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
# buildx bake
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx bake [OPTIONS] [TARGET...]
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Build from a file
|
||||||
|
|
||||||
|
### Aliases
|
||||||
|
|
||||||
|
`docker buildx bake`, `docker buildx f`
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Build definition file |
|
||||||
|
| `--load` | | | Shorthand for `--set=*.output=type=docker` |
|
||||||
|
| `--metadata-file` | `string` | | Write build result metadata to the file |
|
||||||
|
| [`--no-cache`](#no-cache) | | | Do not use cache when building the image |
|
||||||
|
| [`--print`](#print) | | | Print the options without building |
|
||||||
|
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||||
|
| [`--pull`](#pull) | | | Always attempt to pull all referenced images |
|
||||||
|
| `--push` | | | Shorthand for `--set=*.output=type=registry` |
|
||||||
|
| [`--set`](#set) | `stringArray` | | Override target value (e.g., `targetpattern.key=value`) |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Bake is a high-level build command. Each specified target will run in parallel
|
||||||
|
as part of the build.
|
||||||
|
|
||||||
|
Read [High-level build options with Bake](https://docs.docker.com/build/bake/)
|
||||||
|
guide for introduction to writing bake files.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> `buildx bake` command may receive backwards incompatible features in the future
|
||||||
|
> if needed. We are looking for feedback on improving the command and extending
|
||||||
|
> the functionality further.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
|
|
||||||
|
### <a name="file"></a> Specify a build definition file (-f, --file)
|
||||||
|
|
||||||
|
Use the `-f` / `--file` option to specify the build definition file to use.
|
||||||
|
The file can be an HCL, JSON or Compose file. If multiple files are specified
|
||||||
|
they are all read and configurations are combined.
|
||||||
|
|
||||||
|
You can pass the names of the targets to build, to build only specific target(s).
|
||||||
|
The following example builds the `db` and `webapp-release` targets that are
|
||||||
|
defined in the `docker-bake.dev.hcl` file:
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
# docker-bake.dev.hcl
|
||||||
|
group "default" {
|
||||||
|
targets = ["db", "webapp-dev"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-dev" {
|
||||||
|
dockerfile = "Dockerfile.webapp"
|
||||||
|
tags = ["docker.io/username/webapp"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "webapp-release" {
|
||||||
|
inherits = ["webapp-dev"]
|
||||||
|
platforms = ["linux/amd64", "linux/arm64"]
|
||||||
|
}
|
||||||
|
|
||||||
|
target "db" {
|
||||||
|
dockerfile = "Dockerfile.db"
|
||||||
|
tags = ["docker.io/username/db"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake -f docker-bake.dev.hcl db webapp-release
|
||||||
|
```
|
||||||
|
|
||||||
|
See our [file definition](https://docs.docker.com/build/bake/file-definition/)
|
||||||
|
guide for more details.
|
||||||
|
|
||||||
|
### <a name="no-cache"></a> Do not use cache when building the image (--no-cache)
|
||||||
|
|
||||||
|
Same as `build --no-cache`. Do not use cache when building the image.
|
||||||
|
|
||||||
|
### <a name="print"></a> Print the options without building (--print)
|
||||||
|
|
||||||
|
Prints the resulting options of the targets desired to be built, in a JSON
|
||||||
|
format, without starting a build.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake -f docker-bake.hcl --print db
|
||||||
|
{
|
||||||
|
"group": {
|
||||||
|
"default": {
|
||||||
|
"targets": [
|
||||||
|
"db"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"target": {
|
||||||
|
"db": {
|
||||||
|
"context": "./",
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"tags": [
|
||||||
|
"docker.io/tiborvass/db"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="progress"></a> Set type of progress output (--progress)
|
||||||
|
|
||||||
|
Same as [`build --progress`](buildx_build.md#progress).
|
||||||
|
|
||||||
|
### <a name="pull"></a> Always attempt to pull a newer version of the image (--pull)
|
||||||
|
|
||||||
|
Same as `build --pull`.
|
||||||
|
|
||||||
|
### <a name="set"></a> Override target configurations from command line (--set)
|
||||||
|
|
||||||
|
```
|
||||||
|
--set targetpattern.key[.subkey]=value
|
||||||
|
```
|
||||||
|
|
||||||
|
Override target configurations from command line. The pattern matching syntax
|
||||||
|
is defined in https://golang.org/pkg/path/#Match.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx bake --set target.args.mybuildarg=value
|
||||||
|
$ docker buildx bake --set target.platform=linux/arm64
|
||||||
|
$ docker buildx bake --set foo*.args.mybuildarg=value # overrides build arg for all targets starting with 'foo'
|
||||||
|
$ docker buildx bake --set *.platform=linux/arm64 # overrides platform for all targets
|
||||||
|
$ docker buildx bake --set foo*.no-cache # bypass caching only for targets starting with 'foo'
|
||||||
|
```
|
||||||
|
|
||||||
|
Complete list of overridable fields:
|
||||||
|
|
||||||
|
* `args`
|
||||||
|
* `cache-from`
|
||||||
|
* `cache-to`
|
||||||
|
* `context`
|
||||||
|
* `dockerfile`
|
||||||
|
* `labels`
|
||||||
|
* `no-cache`
|
||||||
|
* `no-cache-filter`
|
||||||
|
* `output`
|
||||||
|
* `platform`
|
||||||
|
* `pull`
|
||||||
|
* `push`
|
||||||
|
* `secrets`
|
||||||
|
* `ssh`
|
||||||
|
* `tags`
|
||||||
|
* `target`
|
||||||
551
docs/reference/buildx_build.md
Normal file
551
docs/reference/buildx_build.md
Normal file
@@ -0,0 +1,551 @@
|
|||||||
|
# buildx build
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx build [OPTIONS] PATH | URL | -
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Start a build
|
||||||
|
|
||||||
|
### Aliases
|
||||||
|
|
||||||
|
`docker buildx build`, `docker buildx b`
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--add-host`](https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) |
|
||||||
|
| [`--allow`](#allow) | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) |
|
||||||
|
| [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables |
|
||||||
|
| [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
| [`--cache-from`](#cache-from) | `stringArray` | | External cache sources (e.g., `user/app:cache`, `type=local,src=path/to/dir`) |
|
||||||
|
| [`--cache-to`](#cache-to) | `stringArray` | | Cache export destinations (e.g., `user/app:cache`, `type=local,dest=path/to/dir`) |
|
||||||
|
| [`--cgroup-parent`](https://docs.docker.com/engine/reference/commandline/build/#use-a-custom-parent-cgroup---cgroup-parent) | `string` | | Optional parent cgroup for the container |
|
||||||
|
| [`-f`](https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f), [`--file`](https://docs.docker.com/engine/reference/commandline/build/#specify-a-dockerfile--f) | `string` | | Name of the Dockerfile (default: `PATH/Dockerfile`) |
|
||||||
|
| `--iidfile` | `string` | | Write the image ID to the file |
|
||||||
|
| `--invoke` | `string` | | Invoke a command after the build [experimental] |
|
||||||
|
| `--label` | `stringArray` | | Set metadata for an image |
|
||||||
|
| [`--load`](#load) | | | Shorthand for `--output=type=docker` |
|
||||||
|
| [`--metadata-file`](#metadata-file) | `string` | | Write build result metadata to the file |
|
||||||
|
| `--network` | `string` | `default` | Set the networking mode for the `RUN` instructions during build |
|
||||||
|
| `--no-cache` | | | Do not use cache when building the image |
|
||||||
|
| `--no-cache-filter` | `stringArray` | | Do not cache specified stages |
|
||||||
|
| [`-o`](#output), [`--output`](#output) | `stringArray` | | Output destination (format: `type=local,dest=path`) |
|
||||||
|
| [`--platform`](#platform) | `stringArray` | | Set target platform for build |
|
||||||
|
| `--print` | `string` | | Print result of information request (e.g., outline, targets) [experimental] |
|
||||||
|
| [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||||
|
| `--pull` | | | Always attempt to pull all referenced images |
|
||||||
|
| [`--push`](#push) | | | Shorthand for `--output=type=registry` |
|
||||||
|
| `-q`, `--quiet` | | | Suppress the build output and print image ID on success |
|
||||||
|
| [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) |
|
||||||
|
| [`--shm-size`](#shm-size) | `bytes` | `0` | Size of `/dev/shm` |
|
||||||
|
| [`--ssh`](#ssh) | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|<id>[=<socket>\|<key>[,<key>]]`) |
|
||||||
|
| [`-t`](https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t), [`--tag`](https://docs.docker.com/engine/reference/commandline/build/#tag-an-image--t) | `stringArray` | | Name and optionally a tag (format: `name:tag`) |
|
||||||
|
| [`--target`](https://docs.docker.com/engine/reference/commandline/build/#specifying-target-build-stage---target) | `string` | | Set the target build stage to build |
|
||||||
|
| [`--ulimit`](#ulimit) | `ulimit` | | Ulimit options |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
Flags marked with `[experimental]` need to be explicitly enabled by setting the
|
||||||
|
`BUILDX_EXPERIMENTAL=1` environment variable.
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
The `buildx build` command starts a build using BuildKit. This command is similar
|
||||||
|
to the UI of `docker build` command and takes the same flags and arguments.
|
||||||
|
|
||||||
|
For documentation on most of these flags, refer to the [`docker build`
|
||||||
|
documentation](https://docs.docker.com/engine/reference/commandline/build/). In
|
||||||
|
here we'll document a subset of the new flags.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="allow"></a> Allow extra privileged entitlement (--allow)
|
||||||
|
|
||||||
|
```
|
||||||
|
--allow=ENTITLEMENT
|
||||||
|
```
|
||||||
|
|
||||||
|
Allow extra privileged entitlement. List of entitlements:
|
||||||
|
|
||||||
|
- `network.host` - Allows executions with host networking.
|
||||||
|
- `security.insecure` - Allows executions without sandbox. See
|
||||||
|
[related Dockerfile extensions](https://docs.docker.com/engine/reference/builder/#run---securitysandbox).
|
||||||
|
|
||||||
|
For entitlements to be enabled, the `buildkitd` daemon also needs to allow them
|
||||||
|
with `--allow-insecure-entitlement` (see [`create --buildkitd-flags`](buildx_create.md#buildkitd-flags))
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create --use --name insecure-builder --buildkitd-flags '--allow-insecure-entitlement security.insecure'
|
||||||
|
$ docker buildx build --allow security.insecure .
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="build-arg"></a> Set build-time variables (--build-arg)
|
||||||
|
|
||||||
|
Same as [`docker build` command](https://docs.docker.com/engine/reference/commandline/build/#set-build-time-variables---build-arg).
|
||||||
|
|
||||||
|
There are also useful built-in build args like:
|
||||||
|
|
||||||
|
* `BUILDKIT_CONTEXT_KEEP_GIT_DIR=<bool>` trigger git context to keep the `.git` directory
|
||||||
|
* `BUILDKIT_INLINE_BUILDINFO_ATTRS=<bool>` inline build info attributes in image config or not
|
||||||
|
* `BUILDKIT_INLINE_CACHE=<bool>` inline cache metadata to image config or not
|
||||||
|
* `BUILDKIT_MULTI_PLATFORM=<bool>` opt into deterministic output regardless of multi-platform output or not
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --build-arg BUILDKIT_MULTI_PLATFORM=1 .
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> More built-in build args can be found in [Dockerfile reference docs](https://docs.docker.com/engine/reference/builder/#buildkit-built-in-build-args).
|
||||||
|
|
||||||
|
### <a name="build-context"></a> Additional build contexts (--build-context)
|
||||||
|
|
||||||
|
```
|
||||||
|
--build-context=name=VALUE
|
||||||
|
```
|
||||||
|
|
||||||
|
Define additional build context with specified contents. In Dockerfile the context can be accessed when `FROM name` or `--from=name` is used.
|
||||||
|
When Dockerfile defines a stage with the same name it is overwritten.
|
||||||
|
|
||||||
|
The value can be a local source directory, [local OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md), container image (with docker-image:// prefix), Git or HTTP URL.
|
||||||
|
|
||||||
|
Replace `alpine:latest` with a pinned one:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --build-context alpine=docker-image://alpine@sha256:0123456789 .
|
||||||
|
```
|
||||||
|
|
||||||
|
Expose a secondary local source directory:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --build-context project=path/to/project/source .
|
||||||
|
# docker buildx build --build-context project=https://github.com/myuser/project.git .
|
||||||
|
```
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
FROM alpine
|
||||||
|
COPY --from=project myfile /
|
||||||
|
```
|
||||||
|
|
||||||
|
#### <a name="source-oci-layout"></a> Source image from OCI layout directory
|
||||||
|
|
||||||
|
Source an image from a local [OCI layout compliant directory](https://github.com/opencontainers/image-spec/blob/main/image-layout.md):
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --build-context foo=oci-layout:///path/to/local/layout@sha256:abcd12345 .
|
||||||
|
```
|
||||||
|
|
||||||
|
```Dockerfile
|
||||||
|
FROM alpine
|
||||||
|
RUN apk add git
|
||||||
|
|
||||||
|
COPY --from=foo myfile /
|
||||||
|
|
||||||
|
FROM foo
|
||||||
|
```
|
||||||
|
|
||||||
|
The OCI layout directory must be compliant with the [OCI layout specification](https://github.com/opencontainers/image-spec/blob/main/image-layout.md). It looks _solely_ for hashes. It does not
|
||||||
|
do any form of `image:tag` resolution to find the hash of the manifest; that is up to you.
|
||||||
|
|
||||||
|
The format of the `--build-context` must be: `<context>=oci-layout://<path-to-local-layout>@sha256:<hash-of-manifest>`, where:
|
||||||
|
|
||||||
|
* `context` is the name of the build context as used in the `Dockerfile`.
|
||||||
|
* `path-to-local-layout` is the path on the local machine, where you are running `docker build`, to the spec-compliant OCI layout.
|
||||||
|
* `hash-of-manifest` is the hash of the manifest for the image. It can be a single-architecture manifest or a multi-architecture index.
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
|
|
||||||
|
### <a name="cache-from"></a> Use an external cache source for a build (--cache-from)
|
||||||
|
|
||||||
|
```
|
||||||
|
--cache-from=[NAME|type=TYPE[,KEY=VALUE]]
|
||||||
|
```
|
||||||
|
|
||||||
|
Use an external cache source for a build. Supported types are `registry`,
|
||||||
|
`local` and `gha`.
|
||||||
|
|
||||||
|
- [`registry` source](https://github.com/moby/buildkit#registry-push-image-and-cache-separately)
|
||||||
|
can import cache from a cache manifest or (special) image configuration on the
|
||||||
|
registry.
|
||||||
|
- [`local` source](https://github.com/moby/buildkit#local-directory-1) can
|
||||||
|
import cache from local files previously exported with `--cache-to`.
|
||||||
|
- [`gha` source](https://github.com/moby/buildkit#github-actions-cache-experimental)
|
||||||
|
can import cache from a previously exported cache with `--cache-to` in your
|
||||||
|
GitHub repository
|
||||||
|
|
||||||
|
If no type is specified, `registry` exporter is used with a specified reference.
|
||||||
|
|
||||||
|
`docker` driver currently only supports importing build cache from the registry.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --cache-from=user/app:cache .
|
||||||
|
$ docker buildx build --cache-from=user/app .
|
||||||
|
$ docker buildx build --cache-from=type=registry,ref=user/app .
|
||||||
|
$ docker buildx build --cache-from=type=local,src=path/to/cache .
|
||||||
|
$ docker buildx build --cache-from=type=gha .
|
||||||
|
```
|
||||||
|
|
||||||
|
More info about cache exporters and available attributes: https://github.com/moby/buildkit#export-cache
|
||||||
|
|
||||||
|
### <a name="cache-to"></a> Export build cache to an external cache destination (--cache-to)
|
||||||
|
|
||||||
|
```
|
||||||
|
--cache-to=[NAME|type=TYPE[,KEY=VALUE]]
|
||||||
|
```
|
||||||
|
|
||||||
|
Export build cache to an external cache destination. Supported types are
|
||||||
|
`registry`, `local`, `inline` and `gha`.
|
||||||
|
|
||||||
|
- [`registry` type](https://github.com/moby/buildkit#registry-push-image-and-cache-separately) exports build cache to a cache manifest in the registry.
|
||||||
|
- [`local` type](https://github.com/moby/buildkit#local-directory-1) type
|
||||||
|
exports cache to a local directory on the client.
|
||||||
|
- [`inline` type](https://github.com/moby/buildkit#inline-push-image-and-cache-together)
|
||||||
|
type writes the cache metadata into the image configuration.
|
||||||
|
- [`gha` type](https://github.com/moby/buildkit#github-actions-cache-experimental)
|
||||||
|
type exports cache through the [Github Actions Cache service API](https://github.com/tonistiigi/go-actions-cache/blob/master/api.md#authentication).
|
||||||
|
|
||||||
|
`docker` driver currently only supports exporting inline cache metadata to image
|
||||||
|
configuration. Alternatively, `--build-arg BUILDKIT_INLINE_CACHE=1` can be used
|
||||||
|
to trigger inline cache exporter.
|
||||||
|
|
||||||
|
Attribute key:
|
||||||
|
|
||||||
|
- `mode` - Specifies how many layers are exported with the cache. `min` on only
|
||||||
|
exports layers already in the final build stage, `max` exports layers for
|
||||||
|
all stages. Metadata is always exported for the whole build.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --cache-to=user/app:cache .
|
||||||
|
$ docker buildx build --cache-to=type=inline .
|
||||||
|
$ docker buildx build --cache-to=type=registry,ref=user/app .
|
||||||
|
$ docker buildx build --cache-to=type=local,dest=path/to/cache .
|
||||||
|
$ docker buildx build --cache-to=type=gha .
|
||||||
|
```
|
||||||
|
|
||||||
|
More info about cache exporters and available attributes: https://github.com/moby/buildkit#export-cache
|
||||||
|
|
||||||
|
### <a name="load"></a> Load the single-platform build result to `docker images` (--load)
|
||||||
|
|
||||||
|
Shorthand for [`--output=type=docker`](#docker). Will automatically load the
|
||||||
|
single-platform build result to `docker images`.
|
||||||
|
|
||||||
|
### <a name="metadata-file"></a> Write build result metadata to the file (--metadata-file)
|
||||||
|
|
||||||
|
To output build metadata such as the image digest, pass the `--metadata-file` flag.
|
||||||
|
The metadata will be written as a JSON object to the specified file. The
|
||||||
|
directory of the specified file must already exist and be writable.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --load --metadata-file metadata.json .
|
||||||
|
$ cat metadata.json
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"containerimage.buildinfo": {
|
||||||
|
"frontend": "dockerfile.v0",
|
||||||
|
"attrs": {
|
||||||
|
"context": "https://github.com/crazy-max/buildkit-buildsources-test.git#master",
|
||||||
|
"filename": "Dockerfile",
|
||||||
|
"source": "docker/dockerfile:master"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "docker-image",
|
||||||
|
"ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0",
|
||||||
|
"pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docker-image",
|
||||||
|
"ref": "docker.io/library/alpine:3.13",
|
||||||
|
"pin": "sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"containerimage.config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
|
||||||
|
"containerimage.descriptor": {
|
||||||
|
"annotations": {
|
||||||
|
"config.digest": "sha256:2937f66a9722f7f4a2df583de2f8cb97fc9196059a410e7f00072fc918930e66",
|
||||||
|
"org.opencontainers.image.created": "2022-02-08T21:28:03Z"
|
||||||
|
},
|
||||||
|
"digest": "sha256:19ffeab6f8bc9293ac2c3fdf94ebe28396254c993aea0b5a542cfb02e0883fa3",
|
||||||
|
"mediaType": "application/vnd.oci.image.manifest.v1+json",
|
||||||
|
"size": 506
|
||||||
|
},
|
||||||
|
"containerimage.digest": "sha256:19ffeab6f8bc9293ac2c3fdf94ebe28396254c993aea0b5a542cfb02e0883fa3"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="output"></a> Set the export action for the build result (-o, --output)
|
||||||
|
|
||||||
|
```
|
||||||
|
-o, --output=[PATH,-,type=TYPE[,KEY=VALUE]
|
||||||
|
```
|
||||||
|
|
||||||
|
Sets the export action for the build result. In `docker build` all builds finish
|
||||||
|
by creating a container image and exporting it to `docker images`. `buildx` makes
|
||||||
|
this step configurable allowing results to be exported directly to the client,
|
||||||
|
oci image tarballs, registry etc.
|
||||||
|
|
||||||
|
Buildx with `docker` driver currently only supports local, tarball exporter and
|
||||||
|
image exporter. `docker-container` driver supports all the exporters.
|
||||||
|
|
||||||
|
If just the path is specified as a value, `buildx` will use the local exporter
|
||||||
|
with this path as the destination. If the value is "-", `buildx` will use `tar`
|
||||||
|
exporter and write to `stdout`.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build -o . .
|
||||||
|
$ docker buildx build -o outdir .
|
||||||
|
$ docker buildx build -o - - > out.tar
|
||||||
|
$ docker buildx build -o type=docker .
|
||||||
|
$ docker buildx build -o type=docker,dest=- . > myimage.tar
|
||||||
|
$ docker buildx build -t tonistiigi/foo -o type=registry
|
||||||
|
```
|
||||||
|
|
||||||
|
Supported exported types are:
|
||||||
|
|
||||||
|
#### `local`
|
||||||
|
|
||||||
|
The `local` export type writes all result files to a directory on the client. The
|
||||||
|
new files will be owned by the current user. On multi-platform builds, all results
|
||||||
|
will be put in subdirectories by their platform.
|
||||||
|
|
||||||
|
Attribute key:
|
||||||
|
|
||||||
|
- `dest` - destination directory where files will be written
|
||||||
|
|
||||||
|
#### `tar`
|
||||||
|
|
||||||
|
The `tar` export type writes all result files as a single tarball on the client.
|
||||||
|
On multi-platform builds all results will be put in subdirectories by their platform.
|
||||||
|
|
||||||
|
Attribute key:
|
||||||
|
|
||||||
|
- `dest` - destination path where tarball will be written. “-” writes to stdout.
|
||||||
|
|
||||||
|
#### `oci`
|
||||||
|
|
||||||
|
The `oci` export type writes the result image or manifest list as an [OCI image
|
||||||
|
layout](https://github.com/opencontainers/image-spec/blob/v1.0.1/image-layout.md)
|
||||||
|
tarball on the client.
|
||||||
|
|
||||||
|
Attribute key:
|
||||||
|
|
||||||
|
- `dest` - destination path where tarball will be written. “-” writes to stdout.
|
||||||
|
|
||||||
|
#### `docker`
|
||||||
|
|
||||||
|
The `docker` export type writes the single-platform result image as a [Docker image
|
||||||
|
specification](https://github.com/docker/docker/blob/v20.10.2/image/spec/v1.2.md)
|
||||||
|
tarball on the client. Tarballs created by this exporter are also OCI compatible.
|
||||||
|
|
||||||
|
Currently, multi-platform images cannot be exported with the `docker` export type.
|
||||||
|
The most common usecase for multi-platform images is to directly push to a registry
|
||||||
|
(see [`registry`](#registry)).
|
||||||
|
|
||||||
|
Attribute keys:
|
||||||
|
|
||||||
|
- `dest` - destination path where tarball will be written. If not specified the
|
||||||
|
tar will be loaded automatically to the current docker instance.
|
||||||
|
- `context` - name for the docker context where to import the result
|
||||||
|
|
||||||
|
#### `image`
|
||||||
|
|
||||||
|
The `image` exporter writes the build result as an image or a manifest list. When
|
||||||
|
using `docker` driver the image will appear in `docker images`. Optionally, image
|
||||||
|
can be automatically pushed to a registry by specifying attributes.
|
||||||
|
|
||||||
|
Attribute keys:
|
||||||
|
|
||||||
|
- `name` - name (references) for the new image.
|
||||||
|
- `push` - boolean to automatically push the image.
|
||||||
|
|
||||||
|
#### `registry`
|
||||||
|
|
||||||
|
The `registry` exporter is a shortcut for `type=image,push=true`.
|
||||||
|
|
||||||
|
### <a name="platform"></a> Set the target platforms for the build (--platform)
|
||||||
|
|
||||||
|
```
|
||||||
|
--platform=value[,value]
|
||||||
|
```
|
||||||
|
|
||||||
|
Set the target platform for the build. All `FROM` commands inside the Dockerfile
|
||||||
|
without their own `--platform` flag will pull base images for this platform and
|
||||||
|
this value will also be the platform of the resulting image. The default value
|
||||||
|
will be the current platform of the buildkit daemon.
|
||||||
|
|
||||||
|
When using `docker-container` driver with `buildx`, this flag can accept multiple
|
||||||
|
values as an input separated by a comma. With multiple values the result will be
|
||||||
|
built for all of the specified platforms and joined together into a single manifest
|
||||||
|
list.
|
||||||
|
|
||||||
|
If the `Dockerfile` needs to invoke the `RUN` command, the builder needs runtime
|
||||||
|
support for the specified platform. In a clean setup, you can only execute `RUN`
|
||||||
|
commands for your system architecture.
|
||||||
|
If your kernel supports [`binfmt_misc`](https://en.wikipedia.org/wiki/Binfmt_misc)
|
||||||
|
launchers for secondary architectures, buildx will pick them up automatically.
|
||||||
|
Docker desktop releases come with `binfmt_misc` automatically configured for `arm64`
|
||||||
|
and `arm` architectures. You can see what runtime platforms your current builder
|
||||||
|
instance supports by running `docker buildx inspect --bootstrap`.
|
||||||
|
|
||||||
|
Inside a `Dockerfile`, you can access the current platform value through
|
||||||
|
`TARGETPLATFORM` build argument. Please refer to the [`docker build`
|
||||||
|
documentation](https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope)
|
||||||
|
for the full description of automatic platform argument variants .
|
||||||
|
|
||||||
|
The formatting for the platform specifier is defined in the [containerd source
|
||||||
|
code](https://github.com/containerd/containerd/blob/v1.4.3/platforms/platforms.go#L63).
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --platform=linux/arm64 .
|
||||||
|
$ docker buildx build --platform=linux/amd64,linux/arm64,linux/arm/v7 .
|
||||||
|
$ docker buildx build --platform=darwin .
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="progress"></a> Set type of progress output (--progress)
|
||||||
|
|
||||||
|
```
|
||||||
|
--progress=VALUE
|
||||||
|
```
|
||||||
|
|
||||||
|
Set type of progress output (auto, plain, tty). Use plain to show container
|
||||||
|
output (default "auto").
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> You can also use the `BUILDKIT_PROGRESS` environment variable to set its value.
|
||||||
|
|
||||||
|
The following example uses `plain` output during the build:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --load --progress=plain .
|
||||||
|
|
||||||
|
#1 [internal] load build definition from Dockerfile
|
||||||
|
#1 transferring dockerfile: 227B 0.0s done
|
||||||
|
#1 DONE 0.1s
|
||||||
|
|
||||||
|
#2 [internal] load .dockerignore
|
||||||
|
#2 transferring context: 129B 0.0s done
|
||||||
|
#2 DONE 0.0s
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> Check also our [Color output controls guide](https://github.com/docker/buildx/blob/master/docs/guides/color-output.md)
|
||||||
|
> for modifying the colors that are used to output information to the terminal.
|
||||||
|
|
||||||
|
### <a name="push"></a> Push the build result to a registry (--push)
|
||||||
|
|
||||||
|
Shorthand for [`--output=type=registry`](#registry). Will automatically push the
|
||||||
|
build result to registry.
|
||||||
|
|
||||||
|
### <a name="secret"></a> Secret to expose to the build (--secret)
|
||||||
|
|
||||||
|
```
|
||||||
|
--secret=[type=TYPE[,KEY=VALUE]
|
||||||
|
```
|
||||||
|
|
||||||
|
Exposes secret to the build. The secret can be used by the build using
|
||||||
|
[`RUN --mount=type=secret` mount](https://docs.docker.com/engine/reference/builder/#run---mounttypesecret).
|
||||||
|
|
||||||
|
If `type` is unset it will be detected. Supported types are:
|
||||||
|
|
||||||
|
#### `file`
|
||||||
|
|
||||||
|
Attribute keys:
|
||||||
|
|
||||||
|
- `id` - ID of the secret. Defaults to basename of the `src` path.
|
||||||
|
- `src`, `source` - Secret filename. `id` used if unset.
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1.4
|
||||||
|
FROM python:3
|
||||||
|
RUN pip install awscli
|
||||||
|
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
|
||||||
|
aws s3 cp s3://... ...
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --secret id=aws,src=$HOME/.aws/credentials .
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `env`
|
||||||
|
|
||||||
|
Attribute keys:
|
||||||
|
|
||||||
|
- `id` - ID of the secret. Defaults to `env` name.
|
||||||
|
- `env` - Secret environment variable. `id` used if unset, otherwise will look for `src`, `source` if `id` unset.
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1.4
|
||||||
|
FROM node:alpine
|
||||||
|
RUN --mount=type=bind,target=. \
|
||||||
|
--mount=type=secret,id=SECRET_TOKEN \
|
||||||
|
SECRET_TOKEN=$(cat /run/secrets/SECRET_TOKEN) yarn run test
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ SECRET_TOKEN=token docker buildx build --secret id=SECRET_TOKEN .
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="shm-size"></a> Size of /dev/shm (--shm-size)
|
||||||
|
|
||||||
|
The format is `<number><unit>`. `number` must be greater than `0`. Unit is
|
||||||
|
optional and can be `b` (bytes), `k` (kilobytes), `m` (megabytes), or `g`
|
||||||
|
(gigabytes). If you omit the unit, the system uses bytes.
|
||||||
|
|
||||||
|
### <a name="ssh"></a> SSH agent socket or keys to expose to the build (--ssh)
|
||||||
|
|
||||||
|
```
|
||||||
|
--ssh=default|<id>[=<socket>|<key>[,<key>]]
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be useful when some commands in your Dockerfile need specific SSH
|
||||||
|
authentication (e.g., cloning a private repository).
|
||||||
|
|
||||||
|
`--ssh` exposes SSH agent socket or keys to the build and can be used with the
|
||||||
|
[`RUN --mount=type=ssh` mount](https://docs.docker.com/engine/reference/builder/#run---mounttypessh).
|
||||||
|
|
||||||
|
Example to access Gitlab using an SSH agent socket:
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# syntax=docker/dockerfile:1.4
|
||||||
|
FROM alpine
|
||||||
|
RUN apk add --no-cache openssh-client
|
||||||
|
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
|
||||||
|
RUN --mount=type=ssh ssh -q -T git@gitlab.com 2>&1 | tee /hello
|
||||||
|
# "Welcome to GitLab, @GITLAB_USERNAME_ASSOCIATED_WITH_SSHKEY" should be printed here
|
||||||
|
# with the type of build progress is defined as `plain`.
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ eval $(ssh-agent)
|
||||||
|
$ ssh-add ~/.ssh/id_rsa
|
||||||
|
(Input your passphrase here)
|
||||||
|
$ docker buildx build --ssh default=$SSH_AUTH_SOCK .
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="ulimit"></a> Set ulimits (--ulimit)
|
||||||
|
|
||||||
|
`--ulimit` is specified with a soft and hard limit as such:
|
||||||
|
`<type>=<soft limit>[:<hard limit>]`, for example:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx build --ulimit nofile=1024:1024 .
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> If you do not provide a `hard limit`, the `soft limit` is used
|
||||||
|
> for both values. If no `ulimits` are set, they are inherited from
|
||||||
|
> the default `ulimits` set on the daemon.
|
||||||
229
docs/reference/buildx_create.md
Normal file
229
docs/reference/buildx_create.md
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# buildx create
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx create [OPTIONS] [CONTEXT|ENDPOINT]
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Create a new builder instance
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--append`](#append) | | | Append a node to builder instead of changing it |
|
||||||
|
| `--bootstrap` | | | Boot builder after creation |
|
||||||
|
| [`--buildkitd-flags`](#buildkitd-flags) | `string` | | Flags for buildkitd daemon |
|
||||||
|
| [`--config`](#config) | `string` | | BuildKit config file |
|
||||||
|
| [`--driver`](#driver) | `string` | | Driver to use (available: `docker-container`, `kubernetes`, `remote`) |
|
||||||
|
| [`--driver-opt`](#driver-opt) | `stringArray` | | Options for the driver |
|
||||||
|
| [`--leave`](#leave) | | | Remove a node from builder instead of changing it |
|
||||||
|
| [`--name`](#name) | `string` | | Builder instance name |
|
||||||
|
| [`--node`](#node) | `string` | | Create/modify node with given name |
|
||||||
|
| [`--platform`](#platform) | `stringArray` | | Fixed platforms for current node |
|
||||||
|
| [`--use`](#use) | | | Set the current builder instance |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Create makes a new builder instance pointing to a docker context or endpoint,
|
||||||
|
where context is the name of a context from `docker context ls` and endpoint is
|
||||||
|
the address for docker socket (eg. `DOCKER_HOST` value).
|
||||||
|
|
||||||
|
By default, the current Docker configuration is used for determining the
|
||||||
|
context/endpoint value.
|
||||||
|
|
||||||
|
Builder instances are isolated environments where builds can be invoked. All
|
||||||
|
Docker contexts also get the default builder instance.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="append"></a> Append a new node to an existing builder (--append)
|
||||||
|
|
||||||
|
The `--append` flag changes the action of the command to append a new node to an
|
||||||
|
existing builder specified by `--name`. Buildx will choose an appropriate node
|
||||||
|
for a build based on the platforms it supports.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create mycontext1
|
||||||
|
eager_beaver
|
||||||
|
|
||||||
|
$ docker buildx create --name eager_beaver --append mycontext2
|
||||||
|
eager_beaver
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="buildkitd-flags"></a> Specify options for the buildkitd daemon (--buildkitd-flags)
|
||||||
|
|
||||||
|
```
|
||||||
|
--buildkitd-flags FLAGS
|
||||||
|
```
|
||||||
|
|
||||||
|
Adds flags when starting the buildkitd daemon. They take precedence over the
|
||||||
|
configuration file specified by [`--config`](#config). See `buildkitd --help`
|
||||||
|
for the available flags.
|
||||||
|
|
||||||
|
```
|
||||||
|
--buildkitd-flags '--debug --debugaddr 0.0.0.0:6666'
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="config"></a> Specify a configuration file for the buildkitd daemon (--config)
|
||||||
|
|
||||||
|
```
|
||||||
|
--config FILE
|
||||||
|
```
|
||||||
|
|
||||||
|
Specifies the configuration file for the buildkitd daemon to use. The configuration
|
||||||
|
can be overridden by [`--buildkitd-flags`](#buildkitd-flags).
|
||||||
|
See an [example buildkitd configuration file](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md).
|
||||||
|
|
||||||
|
If the configuration file is not specified, will look for one by default in:
|
||||||
|
* `$BUILDX_CONFIG/buildkitd.default.toml`
|
||||||
|
* `$DOCKER_CONFIG/buildx/buildkitd.default.toml`
|
||||||
|
* `~/.docker/buildx/buildkitd.default.toml`
|
||||||
|
|
||||||
|
Note that if you create a `docker-container` builder and have specified
|
||||||
|
certificates for registries in the `buildkitd.toml` configuration, the files
|
||||||
|
will be copied into the container under `/etc/buildkit/certs` and configuration
|
||||||
|
will be updated to reflect that.
|
||||||
|
|
||||||
|
### <a name="driver"></a> Set the builder driver to use (--driver)
|
||||||
|
|
||||||
|
```
|
||||||
|
--driver DRIVER
|
||||||
|
```
|
||||||
|
|
||||||
|
Sets the builder driver to be used. There are two available drivers, each have
|
||||||
|
their own specificities.
|
||||||
|
|
||||||
|
#### `docker` driver
|
||||||
|
|
||||||
|
Uses the builder that is built into the docker daemon. With this driver,
|
||||||
|
the [`--load`](buildx_build.md#load) flag is implied by default on
|
||||||
|
`buildx build`. However, building multi-platform images or exporting cache is
|
||||||
|
not currently supported.
|
||||||
|
|
||||||
|
#### `docker-container` driver
|
||||||
|
|
||||||
|
Uses a BuildKit container that will be spawned via docker. With this driver,
|
||||||
|
both building multi-platform images and exporting cache are supported.
|
||||||
|
|
||||||
|
Unlike `docker` driver, built images will not automatically appear in
|
||||||
|
`docker images` and [`build --load`](buildx_build.md#load) needs to be used
|
||||||
|
to achieve that.
|
||||||
|
|
||||||
|
#### `kubernetes` driver
|
||||||
|
|
||||||
|
Uses a kubernetes pods. With this driver, you can spin up pods with defined
|
||||||
|
BuildKit container image to build your images.
|
||||||
|
|
||||||
|
Unlike `docker` driver, built images will not automatically appear in
|
||||||
|
`docker images` and [`build --load`](buildx_build.md#load) needs to be used
|
||||||
|
to achieve that.
|
||||||
|
|
||||||
|
#### `remote` driver
|
||||||
|
|
||||||
|
Uses a remote instance of buildkitd over an arbitrary connection. With this
|
||||||
|
driver, you manually create and manage instances of buildkit yourself, and
|
||||||
|
configure buildx to point at it.
|
||||||
|
|
||||||
|
Unlike `docker` driver, built images will not automatically appear in
|
||||||
|
`docker images` and [`build --load`](buildx_build.md#load) needs to be used
|
||||||
|
to achieve that.
|
||||||
|
|
||||||
|
### <a name="driver-opt"></a> Set additional driver-specific options (--driver-opt)
|
||||||
|
|
||||||
|
```
|
||||||
|
--driver-opt OPTIONS
|
||||||
|
```
|
||||||
|
|
||||||
|
Passes additional driver-specific options.
|
||||||
|
|
||||||
|
Note: When using quoted values for example for the `nodeselector` or
|
||||||
|
`tolerations` options, ensure that quotes are escaped correctly for your shell.
|
||||||
|
|
||||||
|
#### `docker` driver
|
||||||
|
|
||||||
|
No driver options.
|
||||||
|
|
||||||
|
#### `docker-container` driver
|
||||||
|
|
||||||
|
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||||
|
- `network=NETMODE` - Sets the network mode for running the buildkit container.
|
||||||
|
- `cgroup-parent=CGROUP` - Sets the cgroup parent of the buildkit container if docker is using the "cgroupfs" driver. Defaults to `/docker/buildx`.
|
||||||
|
|
||||||
|
#### `kubernetes` driver
|
||||||
|
|
||||||
|
- `image=IMAGE` - Sets the container image to be used for running buildkit.
|
||||||
|
- `namespace=NS` - Sets the Kubernetes namespace. Defaults to the current namespace.
|
||||||
|
- `replicas=N` - Sets the number of `Pod` replicas. Defaults to 1.
|
||||||
|
- `requests.cpu` - Sets the request CPU value specified in units of Kubernetes CPU. Example `requests.cpu=100m`, `requests.cpu=2`
|
||||||
|
- `requests.memory` - Sets the request memory value specified in bytes or with a valid suffix. Example `requests.memory=500Mi`, `requests.memory=4G`
|
||||||
|
- `limits.cpu` - Sets the limit CPU value specified in units of Kubernetes CPU. Example `limits.cpu=100m`, `limits.cpu=2`
|
||||||
|
- `limits.memory` - Sets the limit memory value specified in bytes or with a valid suffix. Example `limits.memory=500Mi`, `limits.memory=4G`
|
||||||
|
- `"nodeselector=label1=value1,label2=value2"` - Sets the kv of `Pod` nodeSelector. No Defaults. Example `nodeselector=kubernetes.io/arch=arm64`
|
||||||
|
- `"tolerations=key=foo,value=bar;key=foo2,operator=exists;key=foo3,effect=NoSchedule"` - Sets the `Pod` tolerations. Accepts the same values as the kube manifest tolera>tions. Key-value pairs are separated by `,`, tolerations are separated by `;`. No Defaults. Example `tolerations=operator=exists`
|
||||||
|
- `rootless=(true|false)` - Run the container as a non-root user without `securityContext.privileged`. Needs Kubernetes 1.19 or later. [Using Ubuntu host kernel is recommended](https://github.com/moby/buildkit/blob/master/docs/rootless.md). Defaults to false.
|
||||||
|
- `loadbalance=(sticky|random)` - Load-balancing strategy. If set to "sticky", the pod is chosen using the hash of the context path. Defaults to "sticky"
|
||||||
|
- `qemu.install=(true|false)` - Install QEMU emulation for multi platforms support.
|
||||||
|
- `qemu.image=IMAGE` - Sets the QEMU emulation image. Defaults to `tonistiigi/binfmt:latest`
|
||||||
|
|
||||||
|
#### `remote` driver
|
||||||
|
|
||||||
|
- `key=KEY` - Sets the TLS client key.
|
||||||
|
- `cert=CERT` - Sets the TLS client certificate to present to buildkitd.
|
||||||
|
- `cacert=CACERT` - Sets the TLS certificate authority used for validation.
|
||||||
|
- `servername=SERVER` - Sets the TLS server name to be used in requests (defaults to the endpoint hostname).
|
||||||
|
|
||||||
|
### <a name="leave"></a> Remove a node from a builder (--leave)
|
||||||
|
|
||||||
|
The `--leave` flag changes the action of the command to remove a node from a
|
||||||
|
builder. The builder needs to be specified with `--name` and node that is removed
|
||||||
|
is set with `--node`.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create --name mybuilder --node mybuilder0 --leave
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="name"></a> Specify the name of the builder (--name)
|
||||||
|
|
||||||
|
```
|
||||||
|
--name NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--name` flag specifies the name of the builder to be created or modified.
|
||||||
|
If none is specified, one will be automatically generated.
|
||||||
|
|
||||||
|
### <a name="node"></a> Specify the name of the node (--node)
|
||||||
|
|
||||||
|
```
|
||||||
|
--node NODE
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--node` flag specifies the name of the node to be created or modified. If
|
||||||
|
none is specified, it is the name of the builder it belongs to, with an index
|
||||||
|
number suffix.
|
||||||
|
|
||||||
|
### <a name="platform"></a> Set the platforms supported by the node (--platform)
|
||||||
|
|
||||||
|
```
|
||||||
|
--platform PLATFORMS
|
||||||
|
```
|
||||||
|
|
||||||
|
The `--platform` flag sets the platforms supported by the node. It expects a
|
||||||
|
comma-separated list of platforms of the form OS/architecture/variant. The node
|
||||||
|
will also automatically detect the platforms it supports, but manual values take
|
||||||
|
priority over the detected ones and can be used when multiple nodes support
|
||||||
|
building for the same platform.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx create --platform linux/amd64
|
||||||
|
$ docker buildx create --platform linux/arm64,linux/arm/v8
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="use"></a> Automatically switch to the newly created builder (--use)
|
||||||
|
|
||||||
|
The `--use` flag automatically switches the current builder to the newly created
|
||||||
|
one. Equivalent to running `docker buildx use $(docker buildx create ...)`.
|
||||||
25
docs/reference/buildx_du.md
Normal file
25
docs/reference/buildx_du.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# buildx du
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx du
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Disk usage
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
| `--filter` | `filter` | | Provide filter values |
|
||||||
|
| `--verbose` | | | Provide a more verbose output |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
36
docs/reference/buildx_imagetools.md
Normal file
36
docs/reference/buildx_imagetools.md
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# buildx imagetools
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx imagetools [OPTIONS] COMMAND
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Commands to work on images in registry
|
||||||
|
|
||||||
|
### Subcommands
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| [`create`](buildx_imagetools_create.md) | Create a new image based on source images |
|
||||||
|
| [`inspect`](buildx_imagetools_inspect.md) | Show details of an image in the registry |
|
||||||
|
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Imagetools contains commands for working with manifest lists in the registry.
|
||||||
|
These commands are useful for inspecting multi-platform build results.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
78
docs/reference/buildx_imagetools_create.md
Normal file
78
docs/reference/buildx_imagetools_create.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
# buildx imagetools create
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx imagetools create [OPTIONS] [SOURCE] [SOURCE...]
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Create a new image based on source images
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--append`](#append) | | | Append to existing manifest |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
| [`--dry-run`](#dry-run) | | | Show final image instead of pushing |
|
||||||
|
| [`-f`](#file), [`--file`](#file) | `stringArray` | | Read source descriptor from file |
|
||||||
|
| `--progress` | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output |
|
||||||
|
| [`-t`](#tag), [`--tag`](#tag) | `stringArray` | | Set reference for new image |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Create a new manifest list based on source manifests. The source manifests can
|
||||||
|
be manifest lists or single platform distribution manifests and must already
|
||||||
|
exist in the registry where the new manifest is created. If only one source is
|
||||||
|
specified, create performs a carbon copy.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="append"></a> Append new sources to an existing manifest list (--append)
|
||||||
|
|
||||||
|
Use the `--append` flag to append the new sources to an existing manifest list
|
||||||
|
in the destination.
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
|
|
||||||
|
### <a name="dry-run"></a> Show final image instead of pushing (--dry-run)
|
||||||
|
|
||||||
|
Use the `--dry-run` flag to not push the image, just show it.
|
||||||
|
|
||||||
|
### <a name="file"></a> Read source descriptor from a file (-f, --file)
|
||||||
|
|
||||||
|
```
|
||||||
|
-f FILE or --file FILE
|
||||||
|
```
|
||||||
|
|
||||||
|
Reads source from files. A source can be a manifest digest, manifest reference,
|
||||||
|
or a JSON of OCI descriptor object.
|
||||||
|
|
||||||
|
In order to define annotations or additional platform properties like `os.version` and
|
||||||
|
`os.features` you need to add them in the OCI descriptor object encoded in JSON.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect --raw alpine | jq '.manifests[0] | .platform."os.version"="10.1"' > descr.json
|
||||||
|
$ docker buildx imagetools create -f descr.json myuser/image
|
||||||
|
```
|
||||||
|
|
||||||
|
The descriptor in the file is merged with existing descriptor in the registry if it exists.
|
||||||
|
|
||||||
|
The supported fields for the descriptor are defined in [OCI spec](https://github.com/opencontainers/image-spec/blob/master/descriptor.md#properties) .
|
||||||
|
|
||||||
|
### <a name="tag"></a> Set reference for new image (-t, --tag)
|
||||||
|
|
||||||
|
```
|
||||||
|
-t IMAGE or --tag IMAGE
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the `-t` or `--tag` flag to set the name of the image to be created.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools create --dry-run alpine@sha256:5c40b3c27b9f13c873fefb2139765c56ce97fd50230f1f2d5c91e55dec171907 sha256:c4ba6347b0e4258ce6a6de2401619316f982b7bcc529f73d2a410d0097730204
|
||||||
|
$ docker buildx imagetools create -t tonistiigi/myapp -f image1 -f image2
|
||||||
|
```
|
||||||
632
docs/reference/buildx_imagetools_inspect.md
Normal file
632
docs/reference/buildx_imagetools_inspect.md
Normal file
@@ -0,0 +1,632 @@
|
|||||||
|
# buildx imagetools inspect
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx imagetools inspect [OPTIONS] NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Show details of an image in the registry
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
| [`--format`](#format) | `string` | `{{.Manifest}}` | Format the output using the given Go template |
|
||||||
|
| [`--raw`](#raw) | | | Show original, unformatted JSON manifest |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Show details of an image in the registry.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect alpine
|
||||||
|
Name: docker.io/library/alpine:latest
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
|
||||||
|
Digest: sha256:21a3deaa0d32a8057914f36584b5288d2e5ecc984380bc0118285c70fa8c9300
|
||||||
|
|
||||||
|
Manifests:
|
||||||
|
Name: docker.io/library/alpine:latest@sha256:e7d88de73db3d3fd9b2d63aa7f447a10fd0220b7cbf39803c803f2af9ba256b3
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/amd64
|
||||||
|
|
||||||
|
Name: docker.io/library/alpine:latest@sha256:e047bc2af17934d38c5a7fa9f46d443f1de3a7675546402592ef805cfa929f9d
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/arm/v6
|
||||||
|
|
||||||
|
Name: docker.io/library/alpine:latest@sha256:8483ecd016885d8dba70426fda133c30466f661bb041490d525658f1aac73822
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/arm/v7
|
||||||
|
|
||||||
|
Name: docker.io/library/alpine:latest@sha256:c74f1b1166784193ea6c8f9440263b9be6cae07dfe35e32a5df7a31358ac2060
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/arm64/v8
|
||||||
|
|
||||||
|
Name: docker.io/library/alpine:latest@sha256:2689e157117d2da668ad4699549e55eba1ceb79cb7862368b30919f0488213f4
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/386
|
||||||
|
|
||||||
|
Name: docker.io/library/alpine:latest@sha256:2042a492bcdd847a01cd7f119cd48caa180da696ed2aedd085001a78664407d6
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/ppc64le
|
||||||
|
|
||||||
|
Name: docker.io/library/alpine:latest@sha256:49e322ab6690e73a4909f787bcbdb873631264ff4a108cddfd9f9c249ba1d58e
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/s390x
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
|
|
||||||
|
### <a name="format"></a> Format the output (--format)
|
||||||
|
|
||||||
|
Format the output using the given Go template. Defaults to `{{.Manifest}}` if
|
||||||
|
unset. Following fields are available:
|
||||||
|
|
||||||
|
* `.Name`: provides the reference of the image
|
||||||
|
* `.Manifest`: provides the manifest or manifest list
|
||||||
|
* `.Image`: provides the image config
|
||||||
|
* `.BuildInfo`: provides [build info from image config](https://github.com/moby/buildkit/blob/master/docs/build-repro.md#image-config)
|
||||||
|
|
||||||
|
#### `.Name`
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect alpine --format "{{.Name}}"
|
||||||
|
Name: docker.io/library/alpine:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `.Manifest`
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect crazymax/loop --format "{{.Manifest}}"
|
||||||
|
Name: docker.io/crazymax/loop:latest
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Digest: sha256:08602e7340970e92bde5e0a2e887c1fde4d9ae753d1e05efb4c8ef3b609f97f1
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect moby/buildkit:master --format "{{.Manifest}}"
|
||||||
|
Name: docker.io/moby/buildkit:master
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.list.v2+json
|
||||||
|
Digest: sha256:3183f7ce54d1efb44c34b84f428ae10aaf141e553c6b52a7ff44cc7083a05a66
|
||||||
|
|
||||||
|
Manifests:
|
||||||
|
Name: docker.io/moby/buildkit:master@sha256:667d28c9fb33820ce686887a717a148e89fa77f9097f9352996bbcce99d352b1
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/amd64
|
||||||
|
|
||||||
|
Name: docker.io/moby/buildkit:master@sha256:71789527b64ab3d7b3de01d364b449cd7f7a3da758218fbf73b9c9aae05a6775
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/arm/v7
|
||||||
|
|
||||||
|
Name: docker.io/moby/buildkit:master@sha256:fb64667e1ce6ab0d05478f3a8402af07b27737598dcf9a510fb1d792b13a66be
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/arm64
|
||||||
|
|
||||||
|
Name: docker.io/moby/buildkit:master@sha256:1c3ddf95a0788e23f72f25800c05abc4458946685e2b66788c3d978cde6da92b
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/s390x
|
||||||
|
|
||||||
|
Name: docker.io/moby/buildkit:master@sha256:05bcde6d460a284e5bc88026cd070277e8380355de3126cbc8fe8a452708c6b1
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/ppc64le
|
||||||
|
|
||||||
|
Name: docker.io/moby/buildkit:master@sha256:c04c57765304ab84f4f9807fff3e11605c3a60e16435c734b02c723680f6bd6e
|
||||||
|
MediaType: application/vnd.docker.distribution.manifest.v2+json
|
||||||
|
Platform: linux/riscv64
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `.BuildInfo`
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{.BuildInfo}}"
|
||||||
|
Name: docker.io/crazymax/buildx:buildinfo
|
||||||
|
Frontend: dockerfile.v0
|
||||||
|
Attrs:
|
||||||
|
filename: Dockerfile
|
||||||
|
source: docker/dockerfile-upstream:master-labs
|
||||||
|
build-arg:bar: foo
|
||||||
|
build-arg:foo: bar
|
||||||
|
Sources:
|
||||||
|
Type: docker-image
|
||||||
|
Ref: docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0
|
||||||
|
Pin: sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0
|
||||||
|
|
||||||
|
Type: docker-image
|
||||||
|
Ref: docker.io/library/alpine:3.13
|
||||||
|
Pin: sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c
|
||||||
|
|
||||||
|
Type: docker-image
|
||||||
|
Ref: docker.io/moby/buildkit:v0.9.0
|
||||||
|
Pin: sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab
|
||||||
|
|
||||||
|
Type: docker-image
|
||||||
|
Ref: docker.io/tonistiigi/xx@sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04
|
||||||
|
Pin: sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04
|
||||||
|
|
||||||
|
Type: http
|
||||||
|
Ref: https://raw.githubusercontent.com/moby/moby/master/README.md
|
||||||
|
Pin: sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c
|
||||||
|
```
|
||||||
|
|
||||||
|
#### JSON output
|
||||||
|
|
||||||
|
A `json` go template func is also available if you want to render fields as
|
||||||
|
JSON bytes:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect crazymax/loop --format "{{json .Manifest}}"
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:08602e7340970e92bde5e0a2e887c1fde4d9ae753d1e05efb4c8ef3b609f97f1",
|
||||||
|
"size": 949
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect moby/buildkit:master --format "{{json .Manifest}}"
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
|
||||||
|
"digest": "sha256:79d97f205e2799d99a3a8ae2a1ef17acb331e11784262c3faada847dc6972c52",
|
||||||
|
"size": 2010,
|
||||||
|
"manifests": [
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:bd1e78f06de26610fadf4eb9d04b1a45a545799d6342701726e952cc0c11c912",
|
||||||
|
"size": 1158,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "amd64",
|
||||||
|
"os": "linux"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:d37dcced63ec0965824fca644f0ac9efad8569434ec15b4c83adfcb3dcfc743b",
|
||||||
|
"size": 1158,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "arm",
|
||||||
|
"os": "linux",
|
||||||
|
"variant": "v7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:ce142eb2255e6af46f2809e159fd03081697c7605a3de03b9cbe9a52ddb244bf",
|
||||||
|
"size": 1158,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "arm64",
|
||||||
|
"os": "linux"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:f59bfb5062fff76ce464bfa4e25ebaaaac887d6818238e119d68613c456d360c",
|
||||||
|
"size": 1158,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "s390x",
|
||||||
|
"os": "linux"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:cc96426e0c50a78105d5637d31356db5dd6ec594f21b24276e534a32da09645c",
|
||||||
|
"size": 1159,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "ppc64le",
|
||||||
|
"os": "linux"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:39f9c1e2878e6c333acb23187d6b205ce82ed934c60da326cb2c698192631478",
|
||||||
|
"size": 1158,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "riscv64",
|
||||||
|
"os": "linux"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{json .BuildInfo}}"
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"frontend": "dockerfile.v0",
|
||||||
|
"attrs": {
|
||||||
|
"build-arg:bar": "foo",
|
||||||
|
"build-arg:foo": "bar",
|
||||||
|
"filename": "Dockerfile",
|
||||||
|
"source": "crazymax/dockerfile:buildattrs"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "docker-image",
|
||||||
|
"ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0",
|
||||||
|
"pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docker-image",
|
||||||
|
"ref": "docker.io/library/alpine:3.13@sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c",
|
||||||
|
"pin": "sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docker-image",
|
||||||
|
"ref": "docker.io/moby/buildkit:v0.9.0@sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab",
|
||||||
|
"pin": "sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docker-image",
|
||||||
|
"ref": "docker.io/tonistiigi/xx@sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04",
|
||||||
|
"pin": "sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"ref": "https://raw.githubusercontent.com/moby/moby/master/README.md",
|
||||||
|
"pin": "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect crazymax/buildx:buildinfo --format "{{json .}}"
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "crazymax/buildx:buildinfo",
|
||||||
|
"manifest": {
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:899d2c7acbc124d406820857bb51d9089717bbe4e22b97eb4bc5789e99f09f83",
|
||||||
|
"size": 2628
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"created": "2022-02-24T12:27:43.627154558Z",
|
||||||
|
"architecture": "amd64",
|
||||||
|
"os": "linux",
|
||||||
|
"config": {
|
||||||
|
"Env": [
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
||||||
|
"DOCKER_TLS_CERTDIR=/certs",
|
||||||
|
"DOCKER_CLI_EXPERIMENTAL=enabled"
|
||||||
|
],
|
||||||
|
"Entrypoint": [
|
||||||
|
"docker-entrypoint.sh"
|
||||||
|
],
|
||||||
|
"Cmd": [
|
||||||
|
"sh"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"rootfs": {
|
||||||
|
"type": "layers",
|
||||||
|
"diff_ids": [
|
||||||
|
"sha256:7fcb75871b2101082203959c83514ac8a9f4ecfee77a0fe9aa73bbe56afdf1b4",
|
||||||
|
"sha256:d3c0b963ff5684160641f936d6a4aa14efc8ff27b6edac255c07f2d03ff92e82",
|
||||||
|
"sha256:3f8d78f13fa9b1f35d3bc3f1351d03a027c38018c37baca73f93eecdea17f244",
|
||||||
|
"sha256:8e6eb1137b182ae0c3f5d40ca46341fda2eaeeeb5fa516a9a2bf96171238e2e0",
|
||||||
|
"sha256:fde4c869a56b54dd76d7352ddaa813fd96202bda30b9dceb2c2f2ad22fa2e6ce",
|
||||||
|
"sha256:52025823edb284321af7846419899234b3c66219bf06061692b709875ed0760f",
|
||||||
|
"sha256:50adb5982dbf6126c7cf279ac3181d1e39fc9116b610b947a3dadae6f7e7c5bc",
|
||||||
|
"sha256:9801c319e1c66c5d295e78b2d3e80547e73c7e3c63a4b71e97c8ca357224af24",
|
||||||
|
"sha256:dfbfac44d5d228c49b42194c8a2f470abd6916d072f612a6fb14318e94fde8ae",
|
||||||
|
"sha256:3dfb74e19dedf61568b917c19b0fd3ee4580870027ca0b6054baf239855d1322",
|
||||||
|
"sha256:b182e707c23e4f19be73f9022a99d2d1ca7bf1ca8f280d40e4d1c10a6f51550e"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"created": "2021-11-12T17:19:58.698676655Z",
|
||||||
|
"created_by": "/bin/sh -c #(nop) ADD file:5a707b9d6cb5fff532e4c2141bc35707593f21da5528c9e71ae2ddb6ba4a4eb6 in / "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2021-11-12T17:19:58.948920855Z",
|
||||||
|
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
|
||||||
|
"empty_layer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:38.285594601Z",
|
||||||
|
"created_by": "RUN /bin/sh -c apk --update --no-cache add bash ca-certificates openssh-client \u0026\u0026 rm -rf /tmp/* /var/cache/apk/* # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:41.061874167Z",
|
||||||
|
"created_by": "COPY /opt/docker/ /usr/local/bin/ # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:41.174098947Z",
|
||||||
|
"created_by": "COPY /usr/bin/buildctl /usr/local/bin/buildctl # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:41.320343683Z",
|
||||||
|
"created_by": "COPY /usr/bin/buildkit* /usr/local/bin/ # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:41.447149933Z",
|
||||||
|
"created_by": "COPY /buildx /usr/libexec/docker/cli-plugins/docker-buildx # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:43.057722191Z",
|
||||||
|
"created_by": "COPY /opt/docker-compose /usr/libexec/docker/cli-plugins/docker-compose # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:43.145224134Z",
|
||||||
|
"created_by": "ADD https://raw.githubusercontent.com/moby/moby/master/README.md / # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:43.422212427Z",
|
||||||
|
"created_by": "ENV DOCKER_TLS_CERTDIR=/certs",
|
||||||
|
"comment": "buildkit.dockerfile.v0",
|
||||||
|
"empty_layer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:43.422212427Z",
|
||||||
|
"created_by": "ENV DOCKER_CLI_EXPERIMENTAL=enabled",
|
||||||
|
"comment": "buildkit.dockerfile.v0",
|
||||||
|
"empty_layer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:43.422212427Z",
|
||||||
|
"created_by": "RUN /bin/sh -c docker --version \u0026\u0026 buildkitd --version \u0026\u0026 buildctl --version \u0026\u0026 docker buildx version \u0026\u0026 docker compose version \u0026\u0026 mkdir /certs /certs/client \u0026\u0026 chmod 1777 /certs /certs/client # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:43.514320155Z",
|
||||||
|
"created_by": "COPY rootfs/modprobe.sh /usr/local/bin/modprobe # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:43.627154558Z",
|
||||||
|
"created_by": "COPY rootfs/docker-entrypoint.sh /usr/local/bin/ # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:43.627154558Z",
|
||||||
|
"created_by": "ENTRYPOINT [\"docker-entrypoint.sh\"]",
|
||||||
|
"comment": "buildkit.dockerfile.v0",
|
||||||
|
"empty_layer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T12:27:43.627154558Z",
|
||||||
|
"created_by": "CMD [\"sh\"]",
|
||||||
|
"comment": "buildkit.dockerfile.v0",
|
||||||
|
"empty_layer": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"buildinfo": {
|
||||||
|
"frontend": "dockerfile.v0",
|
||||||
|
"attrs": {
|
||||||
|
"build-arg:bar": "foo",
|
||||||
|
"build-arg:foo": "bar",
|
||||||
|
"filename": "Dockerfile",
|
||||||
|
"source": "docker/dockerfile-upstream:master-labs"
|
||||||
|
},
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"type": "docker-image",
|
||||||
|
"ref": "docker.io/docker/buildx-bin:0.6.1@sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0",
|
||||||
|
"pin": "sha256:a652ced4a4141977c7daaed0a074dcd9844a78d7d2615465b12f433ae6dd29f0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docker-image",
|
||||||
|
"ref": "docker.io/library/alpine:3.13",
|
||||||
|
"pin": "sha256:026f721af4cf2843e07bba648e158fb35ecc876d822130633cc49f707f0fc88c"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docker-image",
|
||||||
|
"ref": "docker.io/moby/buildkit:v0.9.0",
|
||||||
|
"pin": "sha256:8dc668e7f66db1c044aadbed306020743516a94848793e0f81f94a087ee78cab"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "docker-image",
|
||||||
|
"ref": "docker.io/tonistiigi/xx@sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04",
|
||||||
|
"pin": "sha256:21a61be4744f6531cb5f33b0e6f40ede41fa3a1b8c82d5946178f80cc84bfc04"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "http",
|
||||||
|
"ref": "https://raw.githubusercontent.com/moby/moby/master/README.md",
|
||||||
|
"pin": "sha256:419455202b0ef97e480d7f8199b26a721a417818bc0e2d106975f74323f25e6c"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Multi-platform
|
||||||
|
|
||||||
|
Multi-platform images are supported for `.Image` and `.BuildInfo` fields. If
|
||||||
|
you want to pick up a specific platform, you can specify it using the `index`
|
||||||
|
go template function:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect --format '{{json (index .Image "linux/s390x")}}' moby/buildkit:master
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"created": "2022-02-25T17:13:27.89891722Z",
|
||||||
|
"architecture": "s390x",
|
||||||
|
"os": "linux",
|
||||||
|
"config": {
|
||||||
|
"Env": [
|
||||||
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||||
|
],
|
||||||
|
"Entrypoint": [
|
||||||
|
"buildkitd"
|
||||||
|
],
|
||||||
|
"Volumes": {
|
||||||
|
"/var/lib/buildkit": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rootfs": {
|
||||||
|
"type": "layers",
|
||||||
|
"diff_ids": [
|
||||||
|
"sha256:41048e32d0684349141cf05f629c5fc3c5915d1f3426b66dbb8953a540e01e1e",
|
||||||
|
"sha256:2651209b9208fff6c053bc3c17353cb07874e50f1a9bc96d6afd03aef63de76a",
|
||||||
|
"sha256:6741ed7e73039d853fa8902246a4c7e8bf9dd09652fd1b08251bc5f9e8876a7f",
|
||||||
|
"sha256:92ac046adeeb65c86ae3f0b458dee04ad4a462e417661c04d77642c66494f69b"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"history": [
|
||||||
|
{
|
||||||
|
"created": "2021-11-24T20:41:23.709681315Z",
|
||||||
|
"created_by": "/bin/sh -c #(nop) ADD file:cd24c711a2ef431b3ff94f9a02bfc42f159bc60de1d0eceecafea4e8af02441d in / "
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2021-11-24T20:41:23.94211262Z",
|
||||||
|
"created_by": "/bin/sh -c #(nop) CMD [\"/bin/sh\"]",
|
||||||
|
"empty_layer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-01-26T18:15:21.449825391Z",
|
||||||
|
"created_by": "RUN /bin/sh -c apk add --no-cache fuse3 git openssh pigz xz \u0026\u0026 ln -s fusermount3 /usr/bin/fusermount # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-24T00:34:00.924540012Z",
|
||||||
|
"created_by": "COPY examples/buildctl-daemonless/buildctl-daemonless.sh /usr/bin/ # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-25T17:13:27.89891722Z",
|
||||||
|
"created_by": "VOLUME [/var/lib/buildkit]",
|
||||||
|
"comment": "buildkit.dockerfile.v0",
|
||||||
|
"empty_layer": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-25T17:13:27.89891722Z",
|
||||||
|
"created_by": "COPY / /usr/bin/ # buildkit",
|
||||||
|
"comment": "buildkit.dockerfile.v0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created": "2022-02-25T17:13:27.89891722Z",
|
||||||
|
"created_by": "ENTRYPOINT [\"buildkitd\"]",
|
||||||
|
"comment": "buildkit.dockerfile.v0",
|
||||||
|
"empty_layer": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="raw"></a> Show original, unformatted JSON manifest (--raw)
|
||||||
|
|
||||||
|
Use the `--raw` option to print the unformatted JSON manifest bytes.
|
||||||
|
|
||||||
|
> `jq` is used here to get a better rendering of the output result.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect --raw crazymax/loop | jq
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"config": {
|
||||||
|
"mediaType": "application/vnd.docker.container.image.v1+json",
|
||||||
|
"digest": "sha256:7ace7d324e79b360b2db8b820d83081863d96d22e734cdf297a8e7fd83f6ceb3",
|
||||||
|
"size": 2298
|
||||||
|
},
|
||||||
|
"layers": [
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
|
"digest": "sha256:5843afab387455b37944e709ee8c78d7520df80f8d01cf7f861aae63beeddb6b",
|
||||||
|
"size": 2811478
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
|
"digest": "sha256:726d3732a87e1c430d67e8969de6b222a889d45e045ebae1a008a37ba38f3b1f",
|
||||||
|
"size": 1776812
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
|
||||||
|
"digest": "sha256:5d7cf9b33148a8f220c84f27dd2cfae46aca019a3ea3fbf7274f6d6dbfae8f3b",
|
||||||
|
"size": 382855
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx imagetools inspect --raw moby/buildkit:master | jq
|
||||||
|
```
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
|
||||||
|
"schemaVersion": 2,
|
||||||
|
"manifests": [
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:667d28c9fb33820ce686887a717a148e89fa77f9097f9352996bbcce99d352b1",
|
||||||
|
"size": 1158,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "amd64",
|
||||||
|
"os": "linux"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:71789527b64ab3d7b3de01d364b449cd7f7a3da758218fbf73b9c9aae05a6775",
|
||||||
|
"size": 1158,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "arm",
|
||||||
|
"os": "linux",
|
||||||
|
"variant": "v7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:fb64667e1ce6ab0d05478f3a8402af07b27737598dcf9a510fb1d792b13a66be",
|
||||||
|
"size": 1158,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "arm64",
|
||||||
|
"os": "linux"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:1c3ddf95a0788e23f72f25800c05abc4458946685e2b66788c3d978cde6da92b",
|
||||||
|
"size": 1158,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "s390x",
|
||||||
|
"os": "linux"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:05bcde6d460a284e5bc88026cd070277e8380355de3126cbc8fe8a452708c6b1",
|
||||||
|
"size": 1159,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "ppc64le",
|
||||||
|
"os": "linux"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
|
||||||
|
"digest": "sha256:c04c57765304ab84f4f9807fff3e11605c3a60e16435c734b02c723680f6bd6e",
|
||||||
|
"size": 1158,
|
||||||
|
"platform": {
|
||||||
|
"architecture": "riscv64",
|
||||||
|
"os": "linux"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
68
docs/reference/buildx_inspect.md
Normal file
68
docs/reference/buildx_inspect.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# buildx inspect
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx inspect [NAME]
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Inspect current builder instance
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--bootstrap`](#bootstrap) | | | Ensure builder has booted before inspecting |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Shows information about the current or specified builder.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="bootstrap"></a> Ensure that the builder is running before inspecting (--bootstrap)
|
||||||
|
|
||||||
|
Use the `--bootstrap` option to ensure that the builder is running before
|
||||||
|
inspecting it. If the driver is `docker-container`, then `--bootstrap` starts
|
||||||
|
the buildkit container and waits until it is operational. Bootstrapping is
|
||||||
|
automatically done during build, and therefore not necessary. The same BuildKit
|
||||||
|
container is used during the lifetime of the associated builder node (as
|
||||||
|
displayed in `buildx ls`).
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
|
|
||||||
|
### Get information about a builder instance
|
||||||
|
|
||||||
|
By default, `inspect` shows information about the current builder. Specify the
|
||||||
|
name of the builder to inspect to get information about that builder.
|
||||||
|
The following example shows information about a builder instance named
|
||||||
|
`elated_tesla`:
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> Asterisk `*` next to node build platform(s) indicate they had been set manually during `buildx create`. Otherwise, it had been autodetected.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx inspect elated_tesla
|
||||||
|
|
||||||
|
Name: elated_tesla
|
||||||
|
Driver: docker-container
|
||||||
|
|
||||||
|
Nodes:
|
||||||
|
Name: elated_tesla0
|
||||||
|
Endpoint: unix:///var/run/docker.sock
|
||||||
|
Status: running
|
||||||
|
Buildkit: v0.10.3
|
||||||
|
Platforms: linux/amd64
|
||||||
|
|
||||||
|
Name: elated_tesla1
|
||||||
|
Endpoint: ssh://ubuntu@1.2.3.4
|
||||||
|
Status: running
|
||||||
|
Buildkit: v0.10.3
|
||||||
|
Platforms: linux/arm64*, linux/arm/v7, linux/arm/v6
|
||||||
|
```
|
||||||
11
docs/reference/buildx_install.md
Normal file
11
docs/reference/buildx_install.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# buildx install
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx install
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Install buildx as a 'docker builder' alias
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
29
docs/reference/buildx_ls.md
Normal file
29
docs/reference/buildx_ls.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# buildx ls
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx ls
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
List builder instances
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Lists all builder instances and the nodes for each instance
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx ls
|
||||||
|
NAME/NODE DRIVER/ENDPOINT STATUS BUILDKIT PLATFORMS
|
||||||
|
elated_tesla * docker-container
|
||||||
|
elated_tesla0 unix:///var/run/docker.sock running v0.10.3 linux/amd64
|
||||||
|
elated_tesla1 ssh://ubuntu@1.2.3.4 running v0.10.3 linux/arm64*, linux/arm/v7, linux/arm/v6
|
||||||
|
default docker
|
||||||
|
default default running 20.10.14 linux/amd64
|
||||||
|
```
|
||||||
|
|
||||||
|
Each builder has one or more nodes associated with it. The current builder's
|
||||||
|
name is marked with a `*` in `NAME/NODE` and explicit node to build against for
|
||||||
|
the target platform marked with a `*` in the `PLATFORMS` column.
|
||||||
48
docs/reference/buildx_prune.md
Normal file
48
docs/reference/buildx_prune.md
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# buildx prune
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx prune
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Remove build cache
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `-a`, `--all` | | | Include internal/frontend images |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
| `--filter` | `filter` | | Provide filter values (e.g., `until=24h`) |
|
||||||
|
| `-f`, `--force` | | | Do not prompt for confirmation |
|
||||||
|
| `--keep-storage` | `bytes` | `0` | Amount of disk space to keep for cache |
|
||||||
|
| `--verbose` | | | Provide a more verbose output |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Clears the build cache of the selected builder.
|
||||||
|
|
||||||
|
You can finely control what cache data is kept using:
|
||||||
|
|
||||||
|
- The `--filter=until=<duration>` flag to keep images that have been used in
|
||||||
|
the last `<duration>` time.
|
||||||
|
|
||||||
|
`<duration>` is a duration string, e.g. `24h` or `2h30m`, with allowable
|
||||||
|
units of `(h)ours`, `(m)inutes` and `(s)econds`.
|
||||||
|
|
||||||
|
- The `--keep-storage=<size>` flag to keep `<size>` bytes of data in the cache.
|
||||||
|
|
||||||
|
`<size>` is a human-readable memory string, e.g. `128mb`, `2gb`, etc. Units
|
||||||
|
are case-insensitive.
|
||||||
|
|
||||||
|
- The `--all` flag to allow clearing internal helper images and frontend images
|
||||||
|
set using the `#syntax=` directive or the `BUILDKIT_SYNTAX` build argument.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
59
docs/reference/buildx_rm.md
Normal file
59
docs/reference/buildx_rm.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# buildx rm
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx rm [NAME]
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Remove a builder instance
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--all-inactive`](#all-inactive) | | | Remove all inactive builders |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
| [`-f`](#force), [`--force`](#force) | | | Do not prompt for confirmation |
|
||||||
|
| [`--keep-daemon`](#keep-daemon) | | | Keep the buildkitd daemon running |
|
||||||
|
| [`--keep-state`](#keep-state) | | | Keep BuildKit state |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Removes the specified or current builder. It is a no-op attempting to remove the
|
||||||
|
default builder.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="all-inactive"></a> Remove all inactive builders (--all-inactive)
|
||||||
|
|
||||||
|
Remove builders that are not in running state.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx rm --all-inactive
|
||||||
|
WARNING! This will remove all builders that are not in running state. Are you sure you want to continue? [y/N] y
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
|
|
||||||
|
### <a name="force"></a> Do not prompt for confirmation (--force)
|
||||||
|
|
||||||
|
Do not prompt for confirmation before removing inactive builders.
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx rm --all-inactive --force
|
||||||
|
```
|
||||||
|
|
||||||
|
### <a name="keep-daemon"></a> Keep the buildkitd daemon running (--keep-daemon)
|
||||||
|
|
||||||
|
Keep the buildkitd daemon running after the buildx context is removed. This is useful when you manage buildkitd daemons and buildx contexts independently.
|
||||||
|
Currently, only supported by the [`docker-container` and `kubernetes` drivers](buildx_create.md#driver).
|
||||||
|
|
||||||
|
### <a name="keep-state"></a> Keep BuildKit state (--keep-state)
|
||||||
|
|
||||||
|
Keep BuildKit state, so it can be reused by a new builder with the same name.
|
||||||
|
Currently, only supported by the [`docker-container` driver](buildx_create.md#driver).
|
||||||
28
docs/reference/buildx_stop.md
Normal file
28
docs/reference/buildx_stop.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# buildx stop
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx stop [NAME]
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Stop builder instance
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Stops the specified or current builder. This will not prevent buildx build to
|
||||||
|
restart the builder. The implementation of stop depends on the driver.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
11
docs/reference/buildx_uninstall.md
Normal file
11
docs/reference/buildx_uninstall.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# buildx uninstall
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx uninstall
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Uninstall the 'docker builder' alias
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
31
docs/reference/buildx_use.md
Normal file
31
docs/reference/buildx_use.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# buildx use
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx use [OPTIONS] NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Set the current builder instance
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| [`--builder`](#builder) | `string` | | Override the configured builder instance |
|
||||||
|
| `--default` | | | Set builder as default for current context |
|
||||||
|
| `--global` | | | Builder persists context changes |
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Switches the current builder instance. Build commands invoked after this command
|
||||||
|
will run on a specified builder. Alternatively, a context name can be used to
|
||||||
|
switch to the default builder of that context.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### <a name="builder"></a> Override the configured builder instance (--builder)
|
||||||
|
|
||||||
|
Same as [`buildx --builder`](buildx.md#builder).
|
||||||
20
docs/reference/buildx_version.md
Normal file
20
docs/reference/buildx_version.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# buildx version
|
||||||
|
|
||||||
|
```
|
||||||
|
docker buildx version
|
||||||
|
```
|
||||||
|
|
||||||
|
<!---MARKER_GEN_START-->
|
||||||
|
Show buildx version information
|
||||||
|
|
||||||
|
|
||||||
|
<!---MARKER_GEN_END-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
View version information
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ docker buildx version
|
||||||
|
github.com/docker/buildx v0.5.1-docker 11057da37336192bfc57d81e02359ba7ba848e4a
|
||||||
|
```
|
||||||
@@ -2,5 +2,6 @@ package bkimage
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultImage = "moby/buildkit:buildx-stable-1" // TODO: make this verified
|
DefaultImage = "moby/buildkit:buildx-stable-1" // TODO: make this verified
|
||||||
DefaultRootlessImage = "moby/buildkit:v0.6.2-rootless"
|
QemuImage = "tonistiigi/binfmt:latest" // TODO: make this verified
|
||||||
|
DefaultRootlessImage = DefaultImage + "-rootless"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,35 +1,47 @@
|
|||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
"github.com/docker/buildx/driver/bkimage"
|
"github.com/docker/buildx/driver/bkimage"
|
||||||
|
"github.com/docker/buildx/util/confutil"
|
||||||
"github.com/docker/buildx/util/imagetools"
|
"github.com/docker/buildx/util/imagetools"
|
||||||
"github.com/docker/buildx/util/progress"
|
"github.com/docker/buildx/util/progress"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
dockertypes "github.com/docker/docker/api/types"
|
dockertypes "github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/mount"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
|
dockerarchive "github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/idtools"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/moby/buildkit/client"
|
"github.com/moby/buildkit/client"
|
||||||
|
"github.com/moby/buildkit/util/tracing/detect"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
volumeStateSuffix = "_state"
|
||||||
|
)
|
||||||
|
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
driver.InitConfig
|
driver.InitConfig
|
||||||
factory driver.Factory
|
factory driver.Factory
|
||||||
netMode string
|
userNSRemap bool // true if dockerd is running with userns-remap mode
|
||||||
image string
|
netMode string
|
||||||
env []string
|
image string
|
||||||
|
cgroupParent string
|
||||||
|
env []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) IsMobyDriver() bool {
|
func (d *Driver) IsMobyDriver() bool {
|
||||||
@@ -78,7 +90,7 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
_, err = io.Copy(ioutil.Discard, rc)
|
_, err = io.Copy(io.Discard, rc)
|
||||||
return err
|
return err
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
// image pulling failed, check if it exists in local image store.
|
// image pulling failed, check if it exists in local image store.
|
||||||
@@ -101,23 +113,34 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
|
|||||||
if err := l.Wrap("creating container "+d.Name, func() error {
|
if err := l.Wrap("creating container "+d.Name, func() error {
|
||||||
hc := &container.HostConfig{
|
hc := &container.HostConfig{
|
||||||
Privileged: true,
|
Privileged: true,
|
||||||
UsernsMode: "host",
|
Mounts: []mount.Mount{
|
||||||
|
{
|
||||||
|
Type: mount.TypeVolume,
|
||||||
|
Source: d.Name + volumeStateSuffix,
|
||||||
|
Target: confutil.DefaultBuildKitStateDir,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if d.userNSRemap {
|
||||||
|
hc.UsernsMode = "host"
|
||||||
}
|
}
|
||||||
if d.netMode != "" {
|
if d.netMode != "" {
|
||||||
hc.NetworkMode = container.NetworkMode(d.netMode)
|
hc.NetworkMode = container.NetworkMode(d.netMode)
|
||||||
}
|
}
|
||||||
|
if info, err := d.DockerAPI.Info(ctx); err == nil && info.CgroupDriver == "cgroupfs" {
|
||||||
|
// Place all buildkit containers inside this cgroup by default so limits can be attached
|
||||||
|
// to all build activity on the host.
|
||||||
|
hc.CgroupParent = "/docker/buildx"
|
||||||
|
if d.cgroupParent != "" {
|
||||||
|
hc.CgroupParent = d.cgroupParent
|
||||||
|
}
|
||||||
|
}
|
||||||
_, err := d.DockerAPI.ContainerCreate(ctx, cfg, hc, &network.NetworkingConfig{}, nil, d.Name)
|
_, err := d.DockerAPI.ContainerCreate(ctx, cfg, hc, &network.NetworkingConfig{}, nil, d.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if f := d.InitConfig.ConfigFile; f != "" {
|
if err := d.copyToContainer(ctx, d.InitConfig.Files); err != nil {
|
||||||
buf, err := readFileToTar(f)
|
return err
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := d.DockerAPI.CopyToContainer(ctx, d.Name, "/", buf, dockertypes.CopyToContainerOptions{}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err := d.start(ctx, l); err != nil {
|
if err := d.start(ctx, l); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -177,6 +200,24 @@ func (d *Driver) copyLogs(ctx context.Context, l progress.SubLogger) error {
|
|||||||
return rc.Close()
|
return rc.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Driver) copyToContainer(ctx context.Context, files map[string][]byte) error {
|
||||||
|
srcPath, err := writeConfigFiles(files)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if srcPath != "" {
|
||||||
|
defer os.RemoveAll(srcPath)
|
||||||
|
}
|
||||||
|
srcArchive, err := dockerarchive.TarWithOptions(srcPath, &dockerarchive.TarOptions{
|
||||||
|
ChownOpts: &idtools.Identity{UID: 0, GID: 0},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcArchive.Close()
|
||||||
|
return d.DockerAPI.CopyToContainer(ctx, d.Name, "/", srcArchive, dockertypes.CopyToContainerOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, error) {
|
func (d *Driver) exec(ctx context.Context, cmd []string) (string, net.Conn, error) {
|
||||||
execConfig := types.ExecConfig{
|
execConfig := types.ExecConfig{
|
||||||
Cmd: cmd,
|
Cmd: cmd,
|
||||||
@@ -225,7 +266,7 @@ func (d *Driver) start(ctx context.Context, l progress.SubLogger) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
||||||
container, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
|
ctn, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if dockerclient.IsErrNotFound(err) {
|
if dockerclient.IsErrNotFound(err) {
|
||||||
return &driver.Info{
|
return &driver.Info{
|
||||||
@@ -235,7 +276,7 @@ func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if container.State.Running {
|
if ctn.State.Running {
|
||||||
return &driver.Info{
|
return &driver.Info{
|
||||||
Status: driver.Running,
|
Status: driver.Running,
|
||||||
}, nil
|
}, nil
|
||||||
@@ -246,27 +287,59 @@ func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Version(ctx context.Context) (string, error) {
|
||||||
|
bufStdout := &bytes.Buffer{}
|
||||||
|
bufStderr := &bytes.Buffer{}
|
||||||
|
if err := d.run(ctx, []string{"buildkitd", "--version"}, bufStdout, bufStderr); err != nil {
|
||||||
|
if bufStderr.Len() > 0 {
|
||||||
|
return "", errors.Wrap(err, bufStderr.String())
|
||||||
|
}
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
version := strings.Fields(bufStdout.String())
|
||||||
|
if len(version) != 4 {
|
||||||
|
return "", errors.Errorf("unexpected version format: %s", bufStdout.String())
|
||||||
|
}
|
||||||
|
return version[2], nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
||||||
info, err := d.Info(ctx)
|
info, err := d.Info(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if info.Status == driver.Running {
|
if info.Status == driver.Running {
|
||||||
return d.DockerAPI.ContainerStop(ctx, d.Name, nil)
|
return d.DockerAPI.ContainerStop(ctx, d.Name, container.StopOptions{})
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Rm(ctx context.Context, force bool) error {
|
func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
||||||
info, err := d.Info(ctx)
|
info, err := d.Info(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if info.Status != driver.Inactive {
|
if info.Status != driver.Inactive {
|
||||||
return d.DockerAPI.ContainerRemove(ctx, d.Name, dockertypes.ContainerRemoveOptions{
|
container, err := d.DockerAPI.ContainerInspect(ctx, d.Name)
|
||||||
RemoveVolumes: true,
|
if err != nil {
|
||||||
Force: true,
|
return err
|
||||||
})
|
}
|
||||||
|
if rmDaemon {
|
||||||
|
if err := d.DockerAPI.ContainerRemove(ctx, d.Name, dockertypes.ContainerRemoveOptions{
|
||||||
|
RemoveVolumes: true,
|
||||||
|
Force: force,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, v := range container.Mounts {
|
||||||
|
if v.Name != d.Name+volumeStateSuffix {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if rmVolume {
|
||||||
|
return d.DockerAPI.VolumeRemove(ctx, d.Name+volumeStateSuffix, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -279,9 +352,16 @@ func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
|||||||
|
|
||||||
conn = demuxConn(conn)
|
conn = demuxConn(conn)
|
||||||
|
|
||||||
|
exp, err := detect.Exporter()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
td, _ := exp.(client.TracerDelegate)
|
||||||
|
|
||||||
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||||
return conn, nil
|
return conn, nil
|
||||||
}))
|
}), client.WithTracerDelegate(td))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Factory() driver.Factory {
|
func (d *Driver) Factory() driver.Factory {
|
||||||
@@ -317,29 +397,6 @@ func (d *demux) Read(dt []byte) (int, error) {
|
|||||||
return d.Reader.Read(dt)
|
return d.Reader.Read(dt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func readFileToTar(fn string) (*bytes.Buffer, error) {
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
tw := tar.NewWriter(buf)
|
|
||||||
dt, err := ioutil.ReadFile(fn)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := tw.WriteHeader(&tar.Header{
|
|
||||||
Name: "/etc/buildkit/buildkitd.toml",
|
|
||||||
Size: int64(len(dt)),
|
|
||||||
Mode: 0644,
|
|
||||||
}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if _, err := tw.Write(dt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := tw.Close(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type logWriter struct {
|
type logWriter struct {
|
||||||
logger progress.SubLogger
|
logger progress.SubLogger
|
||||||
stream int
|
stream int
|
||||||
@@ -349,3 +406,27 @@ func (l *logWriter) Write(dt []byte) (int, error) {
|
|||||||
l.logger.Log(l.stream, dt)
|
l.logger.Log(l.stream, dt)
|
||||||
return len(dt), nil
|
return len(dt), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func writeConfigFiles(m map[string][]byte) (_ string, err error) {
|
||||||
|
// Temp dir that will be copied to the container
|
||||||
|
tmpDir, err := os.MkdirTemp("", "buildkitd-config")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
os.RemoveAll(tmpDir)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
for f, dt := range m {
|
||||||
|
f = path.Join(confutil.DefaultBuildKitConfigDir, f)
|
||||||
|
p := filepath.Join(tmpDir, f)
|
||||||
|
if err := os.MkdirAll(filepath.Dir(p), 0700); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(p, dt, 0600); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tmpDir, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/buildx/driver"
|
"github.com/docker/buildx/driver"
|
||||||
|
dockertypes "github.com/docker/docker/api/types"
|
||||||
dockerclient "github.com/docker/docker/client"
|
dockerclient "github.com/docker/docker/client"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@@ -28,7 +29,7 @@ func (*factory) Usage() string {
|
|||||||
return "docker-container"
|
return "docker-container"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*factory) Priority(ctx context.Context, api dockerclient.APIClient) int {
|
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int {
|
||||||
if api == nil {
|
if api == nil {
|
||||||
return priorityUnsupported
|
return priorityUnsupported
|
||||||
}
|
}
|
||||||
@@ -40,6 +41,20 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
|||||||
return nil, errors.Errorf("%s driver requires docker API access", f.Name())
|
return nil, errors.Errorf("%s driver requires docker API access", f.Name())
|
||||||
}
|
}
|
||||||
d := &Driver{factory: f, InitConfig: cfg}
|
d := &Driver{factory: f, InitConfig: cfg}
|
||||||
|
dockerInfo, err := cfg.DockerAPI.Info(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
secOpts, err := dockertypes.DecodeSecurityOptions(dockerInfo.SecurityOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, f := range secOpts {
|
||||||
|
if f.Name == "userns" {
|
||||||
|
d.userNSRemap = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
for k, v := range cfg.DriverOpts {
|
for k, v := range cfg.DriverOpts {
|
||||||
switch {
|
switch {
|
||||||
case k == "network":
|
case k == "network":
|
||||||
@@ -49,6 +64,8 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
|||||||
}
|
}
|
||||||
case k == "image":
|
case k == "image":
|
||||||
d.image = v
|
d.image = v
|
||||||
|
case k == "cgroup-parent":
|
||||||
|
d.cgroupParent = v
|
||||||
case strings.HasPrefix(k, "env."):
|
case strings.HasPrefix(k, "env."):
|
||||||
envName := strings.TrimPrefix(k, "env.")
|
envName := strings.TrimPrefix(k, "env.")
|
||||||
if envName == "" {
|
if envName == "" {
|
||||||
|
|||||||
@@ -29,26 +29,47 @@ func (d *Driver) Info(ctx context.Context) (*driver.Info, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Driver) Version(ctx context.Context) (string, error) {
|
||||||
|
v, err := d.DockerAPI.ServerVersion(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrapf(driver.ErrNotConnecting, err.Error())
|
||||||
|
}
|
||||||
|
return v.Version, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
func (d *Driver) Stop(ctx context.Context, force bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Rm(ctx context.Context, force bool) error {
|
func (d *Driver) Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
func (d *Driver) Client(ctx context.Context) (*client.Client, error) {
|
||||||
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
return client.New(ctx, "", client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
|
||||||
return d.DockerAPI.DialHijack(ctx, "/grpc", "h2c", nil)
|
return d.DockerAPI.DialHijack(ctx, "/grpc", "h2c", nil)
|
||||||
|
}), client.WithSessionDialer(func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
|
||||||
|
return d.DockerAPI.DialHijack(ctx, "/session", proto, meta)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Features() map[driver.Feature]bool {
|
func (d *Driver) Features() map[driver.Feature]bool {
|
||||||
|
var useContainerdSnapshotter bool
|
||||||
|
ctx := context.Background()
|
||||||
|
c, err := d.Client(ctx)
|
||||||
|
if err == nil {
|
||||||
|
workers, _ := c.ListWorkers(ctx)
|
||||||
|
for _, w := range workers {
|
||||||
|
if _, ok := w.Labels["org.mobyproject.buildkit.worker.snapshotter"]; ok {
|
||||||
|
useContainerdSnapshotter = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return map[driver.Feature]bool{
|
return map[driver.Feature]bool{
|
||||||
driver.OCIExporter: false,
|
driver.OCIExporter: useContainerdSnapshotter,
|
||||||
driver.DockerExporter: false,
|
driver.DockerExporter: useContainerdSnapshotter,
|
||||||
driver.CacheExport: false,
|
driver.CacheExport: useContainerdSnapshotter,
|
||||||
driver.MultiPlatform: false,
|
driver.MultiPlatform: useContainerdSnapshotter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func (*factory) Usage() string {
|
|||||||
return "docker"
|
return "docker"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*factory) Priority(ctx context.Context, api dockerclient.APIClient) int {
|
func (*factory) Priority(ctx context.Context, endpoint string, api dockerclient.APIClient) int {
|
||||||
if api == nil {
|
if api == nil {
|
||||||
return priorityUnsupported
|
return priorityUnsupported
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,7 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
|
|||||||
if cfg.DockerAPI == nil {
|
if cfg.DockerAPI == nil {
|
||||||
return nil, errors.Errorf("docker driver requires docker API access")
|
return nil, errors.Errorf("docker driver requires docker API access")
|
||||||
}
|
}
|
||||||
if cfg.ConfigFile != "" {
|
if len(cfg.Files) > 0 {
|
||||||
return nil, errors.Errorf("setting config file is not supported for docker driver, use dockerd configuration file")
|
return nil, errors.Errorf("setting config file is not supported for docker driver, use dockerd configuration file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,15 +53,16 @@ type Driver interface {
|
|||||||
Factory() Factory
|
Factory() Factory
|
||||||
Bootstrap(context.Context, progress.Logger) error
|
Bootstrap(context.Context, progress.Logger) error
|
||||||
Info(context.Context) (*Info, error)
|
Info(context.Context) (*Info, error)
|
||||||
|
Version(context.Context) (string, error)
|
||||||
Stop(ctx context.Context, force bool) error
|
Stop(ctx context.Context, force bool) error
|
||||||
Rm(ctx context.Context, force bool) error
|
Rm(ctx context.Context, force, rmVolume, rmDaemon bool) error
|
||||||
Client(ctx context.Context) (*client.Client, error)
|
Client(ctx context.Context) (*client.Client, error)
|
||||||
Features() map[Feature]bool
|
Features() map[Feature]bool
|
||||||
IsMobyDriver() bool
|
IsMobyDriver() bool
|
||||||
Config() InitConfig
|
Config() InitConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func Boot(ctx context.Context, d Driver, pw progress.Writer) (*client.Client, error) {
|
func Boot(ctx, clientContext context.Context, d Driver, pw progress.Writer) (*client.Client, error) {
|
||||||
try := 0
|
try := 0
|
||||||
for {
|
for {
|
||||||
info, err := d.Info(ctx)
|
info, err := d.Info(ctx)
|
||||||
@@ -78,7 +79,7 @@ func Boot(ctx context.Context, d Driver, pw progress.Writer) (*client.Client, er
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c, err := d.Client(context.TODO())
|
c, err := d.Client(clientContext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Cause(err) == ErrNotRunning && try <= 2 {
|
if errors.Cause(err) == ErrNotRunning && try <= 2 {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package kubernetes
|
package context
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// KubernetesEndpoint is the kubernetes endpoint name in a stored context
|
// KubernetesEndpoint is the kubernetes endpoint name in a stored context
|
||||||
224
driver/kubernetes/context/endpoint_test.go
Normal file
224
driver/kubernetes/context/endpoint_test.go
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/cli/cli/context"
|
||||||
|
"github.com/docker/cli/cli/context/store"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testEndpoint(server, defaultNamespace string, ca, cert, key []byte, skipTLSVerify bool) Endpoint {
|
||||||
|
var tlsData *context.TLSData
|
||||||
|
if ca != nil || cert != nil || key != nil {
|
||||||
|
tlsData = &context.TLSData{
|
||||||
|
CA: ca,
|
||||||
|
Cert: cert,
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Endpoint{
|
||||||
|
EndpointMeta: EndpointMeta{
|
||||||
|
EndpointMetaBase: context.EndpointMetaBase{
|
||||||
|
Host: server,
|
||||||
|
SkipTLSVerify: skipTLSVerify,
|
||||||
|
},
|
||||||
|
DefaultNamespace: defaultNamespace,
|
||||||
|
},
|
||||||
|
TLSData: tlsData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testStoreCfg = store.NewConfig(
|
||||||
|
func() interface{} {
|
||||||
|
return &map[string]interface{}{}
|
||||||
|
},
|
||||||
|
store.EndpointTypeGetter(KubernetesEndpoint, func() interface{} { return &EndpointMeta{} }),
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSaveLoadContexts(t *testing.T) {
|
||||||
|
storeDir, err := os.MkdirTemp("", "test-load-save-k8-context")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(storeDir)
|
||||||
|
store := store.New(storeDir, testStoreCfg)
|
||||||
|
require.NoError(t, save(store, testEndpoint("https://test", "test", nil, nil, nil, false), "raw-notls"))
|
||||||
|
require.NoError(t, save(store, testEndpoint("https://test", "test", nil, nil, nil, true), "raw-notls-skip"))
|
||||||
|
require.NoError(t, save(store, testEndpoint("https://test", "test", []byte("ca"), []byte("cert"), []byte("key"), true), "raw-tls"))
|
||||||
|
|
||||||
|
kcFile, err := os.CreateTemp(os.TempDir(), "test-load-save-k8-context")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.Remove(kcFile.Name())
|
||||||
|
defer kcFile.Close()
|
||||||
|
cfg := clientcmdapi.NewConfig()
|
||||||
|
cfg.AuthInfos["user"] = clientcmdapi.NewAuthInfo()
|
||||||
|
cfg.Contexts["context1"] = clientcmdapi.NewContext()
|
||||||
|
cfg.Clusters["cluster1"] = clientcmdapi.NewCluster()
|
||||||
|
cfg.Contexts["context2"] = clientcmdapi.NewContext()
|
||||||
|
cfg.Clusters["cluster2"] = clientcmdapi.NewCluster()
|
||||||
|
cfg.AuthInfos["user"].ClientCertificateData = []byte("cert")
|
||||||
|
cfg.AuthInfos["user"].ClientKeyData = []byte("key")
|
||||||
|
cfg.Clusters["cluster1"].Server = "https://server1"
|
||||||
|
cfg.Clusters["cluster1"].InsecureSkipTLSVerify = true
|
||||||
|
cfg.Clusters["cluster2"].Server = "https://server2"
|
||||||
|
cfg.Clusters["cluster2"].CertificateAuthorityData = []byte("ca")
|
||||||
|
cfg.Contexts["context1"].AuthInfo = "user"
|
||||||
|
cfg.Contexts["context1"].Cluster = "cluster1"
|
||||||
|
cfg.Contexts["context1"].Namespace = "namespace1"
|
||||||
|
cfg.Contexts["context2"].AuthInfo = "user"
|
||||||
|
cfg.Contexts["context2"].Cluster = "cluster2"
|
||||||
|
cfg.Contexts["context2"].Namespace = "namespace2"
|
||||||
|
cfg.CurrentContext = "context1"
|
||||||
|
cfgData, err := clientcmd.Write(*cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = kcFile.Write(cfgData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
kcFile.Close()
|
||||||
|
|
||||||
|
epDefault, err := FromKubeConfig(kcFile.Name(), "", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
epContext2, err := FromKubeConfig(kcFile.Name(), "context2", "namespace-override")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, save(store, epDefault, "embed-default-context"))
|
||||||
|
require.NoError(t, save(store, epContext2, "embed-context2"))
|
||||||
|
|
||||||
|
rawNoTLSMeta, err := store.GetMetadata("raw-notls")
|
||||||
|
require.NoError(t, err)
|
||||||
|
rawNoTLSSkipMeta, err := store.GetMetadata("raw-notls-skip")
|
||||||
|
require.NoError(t, err)
|
||||||
|
rawTLSMeta, err := store.GetMetadata("raw-tls")
|
||||||
|
require.NoError(t, err)
|
||||||
|
embededDefaultMeta, err := store.GetMetadata("embed-default-context")
|
||||||
|
require.NoError(t, err)
|
||||||
|
embededContext2Meta, err := store.GetMetadata("embed-context2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
rawNoTLS := EndpointFromContext(rawNoTLSMeta)
|
||||||
|
rawNoTLSSkip := EndpointFromContext(rawNoTLSSkipMeta)
|
||||||
|
rawTLS := EndpointFromContext(rawTLSMeta)
|
||||||
|
embededDefault := EndpointFromContext(embededDefaultMeta)
|
||||||
|
embededContext2 := EndpointFromContext(embededContext2Meta)
|
||||||
|
|
||||||
|
rawNoTLSEP, err := rawNoTLS.WithTLSData(store, "raw-notls")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkClientConfig(t, rawNoTLSEP, "https://test", "test", nil, nil, nil, false)
|
||||||
|
rawNoTLSSkipEP, err := rawNoTLSSkip.WithTLSData(store, "raw-notls-skip")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkClientConfig(t, rawNoTLSSkipEP, "https://test", "test", nil, nil, nil, true)
|
||||||
|
rawTLSEP, err := rawTLS.WithTLSData(store, "raw-tls")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkClientConfig(t, rawTLSEP, "https://test", "test", []byte("ca"), []byte("cert"), []byte("key"), true)
|
||||||
|
embededDefaultEP, err := embededDefault.WithTLSData(store, "embed-default-context")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkClientConfig(t, embededDefaultEP, "https://server1", "namespace1", nil, []byte("cert"), []byte("key"), true)
|
||||||
|
embededContext2EP, err := embededContext2.WithTLSData(store, "embed-context2")
|
||||||
|
require.NoError(t, err)
|
||||||
|
checkClientConfig(t, embededContext2EP, "https://server2", "namespace-override", []byte("ca"), []byte("cert"), []byte("key"), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkClientConfig(t *testing.T, ep Endpoint, server, namespace string, ca, cert, key []byte, skipTLSVerify bool) {
|
||||||
|
config := ep.KubernetesConfig()
|
||||||
|
cfg, err := config.ClientConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
ns, _, _ := config.Namespace()
|
||||||
|
assert.Equal(t, server, cfg.Host)
|
||||||
|
assert.Equal(t, namespace, ns)
|
||||||
|
assert.Equal(t, ca, cfg.CAData)
|
||||||
|
assert.Equal(t, cert, cfg.CertData)
|
||||||
|
assert.Equal(t, key, cfg.KeyData)
|
||||||
|
assert.Equal(t, skipTLSVerify, cfg.Insecure)
|
||||||
|
}
|
||||||
|
|
||||||
|
func save(s store.Writer, ep Endpoint, name string) error {
|
||||||
|
meta := store.Metadata{
|
||||||
|
Endpoints: map[string]interface{}{
|
||||||
|
KubernetesEndpoint: ep.EndpointMeta,
|
||||||
|
},
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
if err := s.CreateOrUpdate(meta); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.ResetEndpointTLSMaterial(name, KubernetesEndpoint, ep.TLSData.ToStoreTLSData())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveLoadGKEConfig(t *testing.T) {
|
||||||
|
storeDir, err := os.MkdirTemp("", t.Name())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(storeDir)
|
||||||
|
store := store.New(storeDir, testStoreCfg)
|
||||||
|
cfg, err := clientcmd.LoadFromFile("fixtures/gke-kubeconfig")
|
||||||
|
require.NoError(t, err)
|
||||||
|
clientCfg := clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{})
|
||||||
|
expectedCfg, err := clientCfg.ClientConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
ep, err := FromKubeConfig("fixtures/gke-kubeconfig", "", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, save(store, ep, "gke-context"))
|
||||||
|
persistedMetadata, err := store.GetMetadata("gke-context")
|
||||||
|
require.NoError(t, err)
|
||||||
|
persistedEPMeta := EndpointFromContext(persistedMetadata)
|
||||||
|
assert.True(t, persistedEPMeta != nil)
|
||||||
|
persistedEP, err := persistedEPMeta.WithTLSData(store, "gke-context")
|
||||||
|
require.NoError(t, err)
|
||||||
|
persistedCfg := persistedEP.KubernetesConfig()
|
||||||
|
actualCfg, err := persistedCfg.ClientConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedCfg.AuthProvider, actualCfg.AuthProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveLoadEKSConfig(t *testing.T) {
|
||||||
|
storeDir, err := os.MkdirTemp("", t.Name())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(storeDir)
|
||||||
|
store := store.New(storeDir, testStoreCfg)
|
||||||
|
cfg, err := clientcmd.LoadFromFile("fixtures/eks-kubeconfig")
|
||||||
|
require.NoError(t, err)
|
||||||
|
clientCfg := clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{})
|
||||||
|
expectedCfg, err := clientCfg.ClientConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
ep, err := FromKubeConfig("fixtures/eks-kubeconfig", "", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, save(store, ep, "eks-context"))
|
||||||
|
persistedMetadata, err := store.GetMetadata("eks-context")
|
||||||
|
require.NoError(t, err)
|
||||||
|
persistedEPMeta := EndpointFromContext(persistedMetadata)
|
||||||
|
assert.True(t, persistedEPMeta != nil)
|
||||||
|
persistedEP, err := persistedEPMeta.WithTLSData(store, "eks-context")
|
||||||
|
require.NoError(t, err)
|
||||||
|
persistedCfg := persistedEP.KubernetesConfig()
|
||||||
|
actualCfg, err := persistedCfg.ClientConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, expectedCfg.ExecProvider, actualCfg.ExecProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSaveLoadK3SConfig(t *testing.T) {
|
||||||
|
storeDir, err := os.MkdirTemp("", t.Name())
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(storeDir)
|
||||||
|
store := store.New(storeDir, testStoreCfg)
|
||||||
|
cfg, err := clientcmd.LoadFromFile("fixtures/k3s-kubeconfig")
|
||||||
|
require.NoError(t, err)
|
||||||
|
clientCfg := clientcmd.NewDefaultClientConfig(*cfg, &clientcmd.ConfigOverrides{})
|
||||||
|
expectedCfg, err := clientCfg.ClientConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
ep, err := FromKubeConfig("fixtures/k3s-kubeconfig", "", "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, save(store, ep, "k3s-context"))
|
||||||
|
persistedMetadata, err := store.GetMetadata("k3s-context")
|
||||||
|
require.NoError(t, err)
|
||||||
|
persistedEPMeta := EndpointFromContext(persistedMetadata)
|
||||||
|
assert.True(t, persistedEPMeta != nil)
|
||||||
|
persistedEP, err := persistedEPMeta.WithTLSData(store, "k3s-context")
|
||||||
|
require.NoError(t, err)
|
||||||
|
persistedCfg := persistedEP.KubernetesConfig()
|
||||||
|
actualCfg, err := persistedCfg.ClientConfig()
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.True(t, len(actualCfg.Username) > 0)
|
||||||
|
assert.True(t, len(actualCfg.Password) > 0)
|
||||||
|
assert.Equal(t, expectedCfg.Username, actualCfg.Username)
|
||||||
|
assert.Equal(t, expectedCfg.Password, actualCfg.Password)
|
||||||
|
}
|
||||||
23
driver/kubernetes/context/fixtures/eks-kubeconfig
Normal file
23
driver/kubernetes/context/fixtures/eks-kubeconfig
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
server: https://some-server
|
||||||
|
name: kubernetes
|
||||||
|
contexts:
|
||||||
|
- context:
|
||||||
|
cluster: kubernetes
|
||||||
|
user: aws
|
||||||
|
name: aws
|
||||||
|
current-context: aws
|
||||||
|
kind: Config
|
||||||
|
preferences: {}
|
||||||
|
users:
|
||||||
|
- name: aws
|
||||||
|
user:
|
||||||
|
exec:
|
||||||
|
apiVersion: client.authentication.k8s.io/v1alpha1
|
||||||
|
command: heptio-authenticator-aws
|
||||||
|
args:
|
||||||
|
- "token"
|
||||||
|
- "-i"
|
||||||
|
- "eks-cf"
|
||||||
23
driver/kubernetes/context/fixtures/gke-kubeconfig
Normal file
23
driver/kubernetes/context/fixtures/gke-kubeconfig
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
clusters:
|
||||||
|
- cluster:
|
||||||
|
server: https://some-server
|
||||||
|
name: gke_sample
|
||||||
|
contexts:
|
||||||
|
- context:
|
||||||
|
cluster: gke_sample
|
||||||
|
user: gke_sample
|
||||||
|
name: gke_sample
|
||||||
|
current-context: gke_sample
|
||||||
|
kind: Config
|
||||||
|
preferences: {}
|
||||||
|
users:
|
||||||
|
- name: gke_sample
|
||||||
|
user:
|
||||||
|
auth-provider:
|
||||||
|
config:
|
||||||
|
cmd-args: config config-helper --format=json
|
||||||
|
cmd-path: /google/google-cloud-sdk/bin/gcloud
|
||||||
|
expiry-key: '{.credential.token_expiry}'
|
||||||
|
token-key: '{.credential.access_token}'
|
||||||
|
name: gcp
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user